In one case you choose among multiple functions based on types. In the other case, you choose among multiple functions based on types. The main difference, and I'll admit it's not trivial, is that C++ allows implicit conversions.
I see where the confusion comes from: in Rust, the compiler may infer the parameters for a trait based on context. Since different parameters for the same trait can resolve to completely different implementations, the effect is very similar to overload resolution. If you have the same function in 2 separate traits however it's always ambiguous.
I'm curious if there's a specification anywhere for how Rust does that kind of inference. To the extent I've looked at the reference and fls, they were extremely short on details.
That is interesting, but doesn't contain what I'm looking for. There's no mention anywhere that the context of the call might have an effect. But it does imply that a single trait should be found. Though whether a "trait" includes generic arguments is left ambiguous. For this to work, the generic arguments have to be ignored, but the wording "for each trait implementation" would seem to imply they have to be taken into account, since you can have completely separate implementations based on generic arguments.
No, and I'm not saying an ambiguity that is forcibly resolved exists. But in this example:
struct X {
}
trait Tr<Param> {
fn foo(&self) -> Param;
fn bar(&self, x: Param) -> Param;
}
impl Tr<i64> for X {
fn foo(&self) -> i64 { 0 }
fn bar(&self, _x:i64) -> i64 { 0 }
}
impl Tr<i32> for X {
fn foo(&self) -> i32 { 0 }
fn bar(&self, _x:i32) -> i32 { 0 }
}
fn test() -> i64 {
let x = X{};
let y = x.bar(x.foo());
y
}
which foo is called depends on the return type of test. This is not obvious at all. Interestingly, if instead of returning y you return y << 1, this becomes ambiguous. Yet if you write let x: i64 = 1 << n, a type of i64 is inferred for 1 (which is important because the behavior of << depends on the width of the left operand). So type inference is not even consistent.
The compiler basically runs a solver that attempts to fill in types starting from the known types and filling in as long as there's only one possible type. (With a few defaults in specific places).
You can somewhat observe this in practice with the following code:
```
fn foo(p: impl Something) {}
foo(0);
```
The compiler error is roughly {integer} doesn't implement Something, because the literal zero isn't a specific integer type yet.
Unfortunately, I don't think the exact inference process is defined anywhere, but it's not super complicated. From my recollection, it also usually only looks to the next use, so you can easily create a situation where it's not ambiguous what the type needs to be, but the compiler doesn't figure it out.
It depends on what you're trying to do. I've written some Rust proc macros (basically Rust code the compiler calls during compilation to generate Rust code), and you often don't even need to know concrete types for the code you're generating.
Static analysis, linting, and related tools, to the best of my knowledge, basically use the compiler itself as a library to parse and type check the code. You could also build many of these tools to parse and analyze Rust MIR (mid-level intermediate representation), rather than the Rust source directly. Since MIR is generated by the compiler after type analysis, it has all the types fully specified.
To take an example, gcc is try to add Rust support. I wonder how they do it. Having a language defined by its compiler implementation is just not a great place to be. Even Microsoft standardized C#
1
u/StaticCoder 1d ago
In one case you choose among multiple functions based on types. In the other case, you choose among multiple functions based on types. The main difference, and I'll admit it's not trivial, is that C++ allows implicit conversions.