4 ms·
This is a good post! A few comments: > Function Overloads Strictly speaking, Rust doesn't support overloaded functions. Function overloading is when you defin
by steveklabnik 2y ago
This is a good post! A few comments:
> Function Overloads
Strictly speaking, Rust doesn't support overloaded functions. Function overloading is when you define the same function but with different arguments, and the language selects the correct one based on the argument type. In this case, it's two different implementations of a trait for two different types. That said, it's close enough that this isn't really an issue, more of just a technical note, since this is a series trying to get into details.
> I can't find an explanation in the Rust documentation but I expect the reason is that someone could implement another trait that provides .into_iter() on whatever the x is in for y in x, thus resulting in a compiler error because there would be two candidate implementations.
Yep, I'm not sure that there is an official explanation anywhere else, but this is exactly what I would assume as well. This ensures that the correct implementation is called. This is also one of the reasons why adding a trait implementation isn't considered a breaking change, even if it could create a compiler error, because you can always expand it yourself to explicitly select the correct choice. Of course, these situations are usually treated more carefully then they have to be, because breakage isn't fun, even if it's technically allowed.
> But wait, you say, I'm doing exactly this in the first program, and indeed you are.
It's not the same, as the next paragraphs explain.
> We are able to examine the function and realize it's safe, but because the compiler wants to use local reasoning, it's not able to do so.
This is a super important point!
- junon 2y ago> I can't find an explanation in the Rust documentation but I expect the reason is that someone could implement another trait that provides .into_iter() on whatever the x is in for y in x, thus resulting in a compiler error because there would be two candidate implementations. Because nothing in Rust is identifier-based. Unlike python, all syntax magic (even the ? operator) relies on traits defined by `core`. for x in y { desugars to let mut iter = IntoIterator::into_iter(y); while let Some(x) = y.next() { and x? desugars to match x { Ok(x) => x, Err(e) => { return Err(From::from(e)); } } and x + y desugars to core::ops::Add::add(x, y) etc. All of those traits are expected to live explicitly in the core crate at well known paths. Otherwise you'd be writing methods with absolutely no idea how the language would interact with it. And if you had a Set type implement `add`, it'd have to accept exactly 2 arguments to be compatible with the language's `add` or something equally as unergonomic. It's traits all the way down! There'd be no explanation needed because it'd be antithetical and contradictory to traits to begin with. Once one understands how traits are intended to be used, the explanation for why there aren't identifier based resolution semantics becomes obvious.
- saagarjha 2y agoSwift has overloading and its own version of traits. None of what you said is actually required.
- norman784 2y agoInteresting, I didn't knew about the for loop and the ops desugars to something in core, I though it was deal in the compiler, but makes sense since you can implement the ops::* traits for your custom types.
- writebetterc 2y agoIt's a bit annoying that there is a standard desugaring of all of this, but there's no actual way of implementing this sugaring yourself. I guess you could have a token macro (or whatever they're called) at the top scope and do it yourself? Anyway, my conclusion is that in order to learn Rust you can't use any syntactic sugar the language provides, those are 'expert features'. There's no way I'd be able to figure out why this happens. Do rust-analyzer provide a way of desugaring these constructs?
- steveklabnik 2y ago> there's no actual way of implementing this sugaring yourself. This is the common case in languages with sugar. Heck, you could consider all of Rust sugar for MIR... > There's no way I'd be able to figure out why this happens. You don't generally need to, but most of this is documented: https://doc.rust-lang.org/stable/std/iter/index.html#for-loops-and-intoiterator https://doc.rust-lang.org/stable/std/iter/index.html#for-loo... https://doc.rust-lang.org/stable/std/ops/trait.Add.html#implementing-add-with-generics https://doc.rust-lang.org/stable/std/ops/trait.Add.html#impl... ?'s desugaring isn't documented because the desugaring is subject to change.
- tialaramex 2y agoSeveral of those de-sugarings are wrong. The book explains how Rust's `for` is actually de-sugared, and that ? is a Try operator, so unsurprisingly it de-sugars to a use of Try::branch and that gets you a ControlFlow which is then pattern matched. Also, they don't need to live "at well known paths". Instead they're marked as "langitems", with the magic attribute lang: #[lang = "add_assign"] // Signifies that this trait is what the compiler wanted to make the += operator work in the language.
- kibwen 2y agoRe: function overloading, Rust doesn't support overloaded functions in the same manner as Java or C++, which is important to emphasize to people coming from those languages. However, if you then show those people that this is legal Rust: let addr1 = Ipv4Addr::from([13, 12, 11, 10]); let addr2 = Ipv4Addr::from(218893066); ...that also runs the risk of confusing them, because this is indistinguishable from function overloading, because it basically is function overloading. I'm not actually sure what to call this, because unlike Java-style overloading, Rust isn't ad-hoc; everything it's doing is fundamentally integrated into the type system. But unlike strict parametric polymorphism (and like Java), both of the above functions have totally distinct implementations.
- zozbot234 2y ago> I'm not actually sure what to call this I'd call it "Making ad-hoc polymorphism less ad hoc".
- demurgos 2y agoMy understanding is that "ad-hoc" in this context means that you are not defining a single generic implementation but adding individual impls for specific types. There's no fundamental difference between Java's overloading and Rust's implicitly inferred trait implementations in my opinion. Rust's implementation is more orthogonal, so specifying which impl you want explicitly does not require special syntax. It's also based on traits so you'd have to use a non-idiomatic style if you wanted to use overloading as pervasively as in Java. But are those really such big differences? See my reply to the original where I post an example using Rust nightly that is very close to overloading in other languages.
- tavianator 2y agoThey were referencing the title of a specific paper, which invented type classes: https://dl.acm.org/doi/10.1145/75277.75283 https://dl.acm.org/doi/10.1145/75277.75283
- 2y ago
- demurgos 2y ago> Strictly speaking, Rust doesn't support overloaded functions. Function overloading is when you define the same function but with different arguments, and the language selects the correct one based on the argument type. In this case, it's two different implementations of a trait for two different types. You're right that there is no function overload in this article, just some implicit derefs. I would argue however that Rust has overloaded functions (function resolution based on argument types) due to how it handles trait resolution with implicit inference. Rust may not have syntax sugar to easily define overloads and people generally try to avoid them, but using argument-based dispatch is extremely common. The most famous example is probably `MyType::from(...)`, but any single-method generic trait using the generics for the method arguments is equivalent to function overloading. There are also other techniques. Using nightly features you can get far enough so a consumer can use native function call syntax. Overload on nightly: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2024&gist=239218207a2f7e8d50491c10bfb92fd0 https://play.rust-lang.org/?version=nightly&mode=debug&editi... Overload on stable: https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=4bc63996f33ca91d555ceffa65f5ad91 https://play.rust-lang.org/?version=stable&mode=debug&editio... The mechanism and syntax may be different from overloading in C++ or Java, but as a user the result is the same and it causes the same pain points.
- steveklabnik 2y ago> The mechanism and syntax may be different from overloading That's all I'm saying. > as a user the result is the same That's why I said I didn't think the inaccuracy is the worst detail.