Rust needs external linkers (e.g. GCC) to generate final output. Why doesn't it provide a bundled one? Are there any languages that does the similar?
Rust Compilation – Why Rust Requires External Linkers and Similar Languages
compilationrust
Related Solutions
What happens in Rust if you define a function that implements a trait but you don't use impl? It just doesn't work?
You need to explicitly implement the trait; happening to have a method with matching name/signature is meaningless for Rust.
Generic call dispatching
Are these the only non-trivial differences? If so, it would appear Go's Interface/Type system is, in practice, not as weak as perceived.
Not providing static dispatch can be a significant performance hit for certain cases (e.g. the Iterator
one I mention below). I think this is what you mean by
Go really has no response here. This is the only thing that is significantly more powerful and it's ultimately just a replacement for copying and pasting methods with different type signatures.
but I'll cover it in more detail, because it's worth understanding the difference deeply.
In Rust
Rust's approach allows for the user to choose between static dispatch and dynamic dispatch. As an example, if you have
trait Foo { fn bar(&self); }
impl Foo for int { fn bar(&self) {} }
impl Foo for String { fn bar(&self) {} }
fn call_bar<T: Foo>(value: T) { value.bar() }
fn main() {
call_bar(1i);
call_bar("foo".to_string());
}
then the two call_bar
calls above will compile to calls to, respectively,
fn call_bar_int(value: int) { value.bar() }
fn call_bar_string(value: String) { value.bar() }
where those .bar()
method calls are static function calls, i.e. to a fixed function address in memory. This allows for optimisations like inlining, because the compiler knows exactly which function is being called. (This is what C++ does too, sometimes called "monomorphisation".)
In Go
Go only allows dynamic dispatch for "generic" functions, that is, the method address is loaded from the value and then called from there, so the exact function is only known at runtime. Using the example above
type Foo interface { bar() }
func call_bar(value Foo) { value.bar() }
type X int;
type Y string;
func (X) bar() {}
func (Y) bar() {}
func main() {
call_bar(X(1))
call_bar(Y("foo"))
}
Now, those two call_bar
s will always be calling the above call_bar
, with the address of bar
loaded from the interface's vtable.
Low-level
To rephrase the above, in C notation. Rust's version creates
/* "implementing" the trait */
void bar_int(...) { ... }
void bar_string(...) { ... }
/* the monomorphised `call_bar` function */
void call_bar_int(int value) {
bar_int(value);
}
void call_bar_string(string value) {
bar_string(value);
}
int main() {
call_bar_int(1);
call_bar_string("foo");
// pretend that is the (hypothetical) `string` type, not a `char*`
return 1;
}
For Go, it's something more like:
/* implementing the interface */
void bar_int(...) { ... }
void bar_string(...) { ... }
// the Foo interface type
struct Foo {
void* data;
struct FooVTable* vtable;
}
struct FooVTable {
void (*bar)(void*);
}
void call_bar(struct Foo value) {
value.vtable.bar(value.data);
}
static struct FooVTable int_vtable = { bar_int };
static struct FooVTable string_vtable = { bar_string };
int main() {
int* i = malloc(sizeof *i);
*i = 1;
struct Foo int_data = { i, &int_vtable };
call_bar(int_data);
string* s = malloc(sizeof *s);
*s = "foo"; // again, pretend the types work
struct Foo string_data = { s, &string_vtable };
call_bar(string_data);
}
(This isn't exactly right---there has to be more information in the vtable---but the method call being a dynamic function pointer is the relevant thing here.)
Rust offers the choice
Going back to
Rust's approach allows for the user to choose between static dispatch and dynamic dispatch.
So far I've only demonstrated Rust having statically dispatched generics, but Rust can opt-in to the dynamic ones like Go (with essentially the same implementation), via trait objects. Notated like &Foo
, which is a borrowed reference to an unknown type that implements the Foo
trait. These values have the same/very similar vtable representation to the Go interface object. (A trait object is an example of an "existential type".)
There are instances where dynamic dispatch is really helpful (and sometimes more performant, by, e.g. reducing code bloat/duplication), but static dispatch allows compilers to inline the callsites and apply all their optimisations, meaning it is normally faster. This is especially important for things like Rust's iteration protocol, where static dispatching trait method calls allows for those iterators to be as fast as the C equivalents, while still seeming high-level and expressive.
Tl;dr: Rust's approach offers both static and dynamic dispatch in generics, at the programmers discretion; Go only allows for dynamic dispatch.
Parametric polymorphism
Furthermore, emphasising traits and deemphasising reflection gives Rust much stronger parametric polymorphism: the programmer knows exactly what a function can do with its arguments, because it has to declare the traits the generic types implement in the function signature.
Go's approach is very flexible, but has fewer guarantees for the callers (making it somewhat harder for the programmer to reason about), because the internals of a function can (and do) query for additional type information (there was a bug in the Go standard library where, iirc, a function taking a writer would use reflection to call Flush
on some inputs, but not others).
Building abstractions
This is somewhat of a sore point, so I'll only talk briefly, but having "proper" generics like Rust has allows for low level data types like Go's map
and []
to actually be implemented directly in the standard library in a strongly typesafe way, and written in Rust (HashMap
and Vec
respectively).
And its not just those types, you can build type-safe generic structures on top of them, e.g. LruCache
is a generic caching layer on top of a hashmap. This means people can just use the data structures directly from the standard library, without having to store data as interface{}
and use type assertions when inserting/extracting. That is, if you have an LruCache<int, String>
, you're guaranteed that the keys are always int
s and the values are always String
s: there's no way to accidentally insert the wrong value (or try to extract a non-String
).
There are major differences between throwing exceptions and returning error objects, in particular,
- exceptions create many new code-flow paths which are not explicitly visible in the source code,
- callers have no idea what kinds of errors they can expect,
- they propagate automatically, and
- they make the code much less verbose and easier to read and write.
Rust tries to get the best of both worlds by banning exceptions (this eliminates the first two points) and by having the try!
macro (this deals with automatic propagation and verbosity).
Your solution deals with the first two points only and there is no straightforward way to have the latter two. Your *
operator is not an equivalent of try!
. The equivalent of
x = try!(foo());
is simply not possible to do in C++ (without language extensions).
That said, feel free to use your class, though as @Deduplicator points out, your Result class can only hold exception objects of dynamic type std::exception
, which is probably not what you intended. The easiest way to accomplish what you want is to use std::exception_ptr
to hold the exception object.
Or, better yet, just use exceptions -- that's what the language was designed for after all.
Best Answer
Rust requires a linker to generate final output. It's only "external" insofar as it is a separate program from the compiler that generates object files.
The same is true for most C and C++ compilers, and probably a bunch of other compiled languages, like Swift, Ada and Fortran.
Using the system linker instead of bundling your own is useful to ensure compatibility. Sure, Rust could bundle LLD, but what would be the advantage over using the system linker on Linux, or bundling MinGW (which is needed anyway) on Windows and using the LD inside? (Or if you go for the MSVC ABI, you will want a Visual Studio installed anyway, so link.exe is available.)
Also, relying on an external compiler driver for the linker invocation simplifies the rustc compiler, since linker invocations tend to be complicated and full of platform-specific black magic. Reimplementing this logic would be a waste of time.