5 ms·
Regarding this line: let addr = "127.0.0.1:12345".parse().unwrap(); Is Rust like Ruby in that devs and library authors can add methods to and redefine bui
by jessedhillon 10y ago
Regarding this line:
let addr = "127.0.0.1:12345".parse().unwrap();
Is Rust like Ruby in that devs and library authors can add methods to and redefine built-ins? And that merely including a library can alter the definition of those objects everywhere in your project's code?
I'm not trying to start a language war, but IMO that's too bad: this feature is, in my experience, the single largest source for difficult-to-detect errors that characterize the Ruby programming experience, e.g. anything that uses Rails. It also contributes to deep stack traces which are essentially useless, because the departure point from your actual code is some 20+ frames above the site of the error.
- steveklabnik 10y ago> Is Rust like Ruby in that devs and library authors can add methods to and redefine built-ins? It's closer to refinements than monkey-patching, that is, you can, but you must bring the trait into scope for it to work. > And that merely including a library can alter the definition of those objects everywhere in your project's code? No, based on the above rule. We don't want that.
- jessedhillon 10y agoWhich line is including the trait to allow the functionality in this example? (taken from https://tokio.rs/ https://tokio.rs/)
- steveklabnik 10y agoIn this case, it's https://github.com/rust-lang/rust/blob/b9637f79e261d0b90d23bdbce6b0a16d42868bdd/src/libstd/net/parser.rs#L19 https://github.com/rust-lang/rust/blob/b9637f79e261d0b90d23b... So, specifically. Rust has "traits", which, for these purposes, work like Ruby modules. But. Rust has "coherence rules." That is, You can only implement a trait for a type if you defined either the trait, or the type, or both. So, because libstd (well, Rust itself, but you get the idea) all three of FromStr, &str, and SocketAddr, it's allowed to do this. You, in your own package, could not define this, since you wouldn't have defined any of them. In tokio-core, they bring SocketAddr into scope: https://docs.rs/tokio-core/0.1.3/src/tokio_core/net/tcp.rs.html#4 https://docs.rs/tokio-core/0.1.3/src/tokio_core/net/tcp.rs.h... However, I was thinking of a slightly different case when I said that, which is methods. That is, you couldn't call methods unless they've had their traits brought into scope. But parse isn't on a trait; it's defined on `str` itself. In that definition, it also has to bring FromStr into scope. So basically, this is very muddled by the fact that this particular example uses the standard library heavily, which already implicitly brings a lot of things (like &str) into scope for you. External packages don't have that.
- dbaupp 10y ago> So, because libstd (well, Rust itself, but you get the idea) all three of FromStr, &str, and SocketAddr, it's allowed to do this. You, in your own package, could not define this, since you wouldn't have defined any of them. It's unclear to me what you're saying here. It's true that you can't make an inherent method `parse` on str like the one being called here, it's also true that one couldn't implement FromStr for SocketAddr in an external package, but one can create a custom generic function that uses FromStr and have that generic type be SocketAddr for some call sites. Of course, if this function wanted to have the syntax of a method call on str, it would have to be defined in a trait that users then import. > In tokio-core, they bring SocketAddr into scope: https://docs.rs/tokio-core/0.1.3/src/tokio_core/net/tcp.rs.h.. https://docs.rs/tokio-core/0.1.3/src/tokio_core/net/tcp.rs.h.... To be clear, this isn't necessary for calling methods---including inherent methods on SocketAddr---unlike the methods in a trait. The only reason to do this is that it lets one abbreviate the name: "SocketAddr" instead of "::std::net::SocketAddr".
- steveklabnik 10y agoAh, duh, good call.
- dbaupp 10y agoOne can't add methods dynamically like Ruby nor redefine built-ins. However, one can implement traits for built-in types and the functionality in such traits is often provided as methods, giving the appearance of adding/overriding built-in functionality. Rust has the important distinction that functionality from a trait is only available when the trait is in scope, which requires an explicit import. Additionally, Rust has a static type checker, which helps lessens the risk of any features that seem implicit. In this case, both of those methods are in the standard library.
- jessedhillon 10y agoSo in this case, someone has created a trait, I guess as part of the standard library, that provides parsing of network addresses from strings? From reading the docs, something provides a method called `FromStr` and the `str.parse` method does some magic to figure out which `FromStr` of all the registered traits is the one which we want? So then, it's not arbitrary monkey-patching, but using a defined protocol for defining and extending string parsing?
- dbaupp 10y agoThe parse method is an inherent method on the string type (i.e. not connected to a trait, and doesn't need an import), but added by the standard library, yes. > From reading the docs, something provides a method called `FromStr` and the `str.parse` method does some magic to figure out which `FromStr` of all the registered traits is the one which we want? The method isn't doing any magic: the language's type inference is deducing that the return type has to be a SocketAddr based on how the return value is used (it is passed to a function expecting a SocketAddr), and this forces the compiler to use the SocketAddr implementation of the FromStr trait (which has a from_str method), all at compile time. (If the compiler deduced that the return type was something that didn't implement FromStr, i.e. no idea how to parse that value from a string, one would get a compile error.) > So then, it's not arbitrary monkey-patching, but using a defined protocol for defining and extending string parsing? Yes, parse is entirely driven by the FromStr trait.