Rust traits for developer friendly libraries
benashford.github.io
benashford.github.io
One thing I'm not sure about though, the article talks about implementing the Into trait, then quickly segues over to From. When would I use Into, when From, and do they both lead to the same end (I.e. a function that takes Into<Whatever>?) I've looked at the docs for each, and maybe I just haven't had enough coffee yet but the distinction isn't too clear.
The standard library contains this code:
// From implies Into
#[stable(feature = "rust1", since = "1.0.0")]
impl<T, U> Into<U> for T where U: From<T> {
fn into(self) -> U {
U::from(self)
}
}
So, you basically only ever implement From, and you get the equivalent Into for free.More specifically, you would usually use `Into` to bound a generic function:
fn is_hello<T: Into<Vec<u8>>>(s: T) {
let bytes = b"hello".to_vec();
assert_eq!(bytes, s.into());
}
let s = "hello".to_string();
is_hello(s);
Whereas you'd use From to perform a conversion: let string = "hello".to_string();
let other_string = String::from("hello");
assert_eq!(string, other_string);
These APIs are pretty new; they landed _right_ before beta happened. We used to have FromFoo traits, and this generic system replaced them.I've always heard that traits in rust are basically just type classes in haskell. But I'm not quite sure how you would implement this trait as a typeclass.
Specifically, from what I'm seeing here is that if you just implement From you've also now implemented Into. Is there an analog in haskell? I also can't figure out how to specify a type class that is parametrized the way into is.
Can anyone help by shedding some light on how you might go about this in haskell? Or where traits in rust differ from typeclasses in haskell?
class Into a b where
into :: a -> b
Note that From would be the exact same class. I guess the distinction in Rust is relevant because of ownership.One thing that's useful about From is that you can implement From<SomePublicStruct> for YourPrivateStruct, but the compiler complains about Into<YourPrivateStruct> for SomePublicStruct.
SomePublicStruct could be (i32, i32) and YourPrivateStruct could be MyPoint, for example.
Making YourPrivateStruct public (with pub struct) fixes things, but that's not always what you want.
The question is - for people reading the code at the call site, how easy is it to grep for what's actually happening?
I guess this rust trait at least hangs off the geobox class, but I think I might prefer the explicit ctor for non write-only code.
In general, I find Rust pretty grep-able, though I'm a bit biased.
You could write a JSON API that doesn't insist on a strict ownership model, if you wanted. There is even a type, MaybeOwned [2], in the standard library to support this kind of API. In such a library, the JSON type would have a lifetime parameter, which could be 'static for owned strings, but which could be non-static for JSON types that contain references to strings.
[1]: https://jansson.readthedocs.org/en/2.7/apiref.html#string
[2]: http://doc.rust-lang.org/0.11.0/collections/str/type.MaybeOw...
In fact, Rust's semantics allows a fewer number of copies than naive use of std::string, since an already-owned value won't be copied, as mentioned above.
In fact, Rust's borrowing model provides some perf gains easily where it would be hard to do so in C++ (http://manishearth.github.io/blog/2015/05/03/where-rust-real...)
(Also, I can't wait until I can download Jai and give it a go, keep putting out videos.)
Actually predicting where data is really going to go involves solving the halting problem. So by necessity any static analysis of ownership is going to be conservative, in the sense that it has to err on the side of safety.
So there's a process of structuring things so that it's not just the programmer who understands, but the compiler who understands. Structuring the code in alternative ways so that ownership is made clear and/or ambiguous cases are resolved. Sometimes this could be a small amount of work, but sometimes it could be a very large amount of work (analogous to the simpler situation in C++ where you are using const everywhere but need to change something deep down in the call tree and now either everyone has to lose their consts or you have to do unsavory things).
At points, it might be possible to structure things so that the compiler would understand them and let you do it, but it would take a large amount of refactoring that one doesn't want to do right now (especially if one has had a few experiences of starting that refactor and having it fail), so instead one might punt and just say "this parameter is owned, problem solved". And that's great, you can stop refactoring, but you just took a performance hit.
Now, in some cases it is probably the case that this is in reality an ambiguous and dangerous ownership situation and the language just did you a favor. But there are also going to be cases where it's not a favor, it's just the understanding of ownership being conservative (because it has to be), and therefore diverging from reality. But I want to get work done today so I make the parameter owned, now the compiler is happy, but there is a performance cost there. If I were not so eager on getting work done today, I might be able to avoid this performance hit by wrestling with the factoring. But I might deem the cost of that prohibitive.
That's all I mean. But like I said, I have never written a large program in Rust so I am not speaking from experience.
I don't know if I agree, per se, but I will say that Rust is still in such early stages that we'll be seeing how it shakes out as more people work with Rust. I haven't found this personally to be an issue, but I've also been doing Rust so long that it's hard to see things from a fresh perspective, you know?
For instance, there is definitely a concrete conversation to be had about the ownership model and implementing data structures -- you even have to use `unsafe {}` for implementing something as simple as a doubly linked list[1]. So that's a concrete example of how single-ownership makes something conceptually simple and safe hard to express in safe code. But in this case, there is so much vagueness about the supposed "massive performance implications" (HP + laziness = massively inefficient) that it comes off as trying a bit hard to... let's just say "to be negative".
> (And really my point is that I perceive there is an ambient pressure toward copying function parameters in general in order to minimize refactoring ... which is what I mean by there being an overall performance impact).
Well admittedly this argument is more concrete.
[1] But that's just a burden for the implementer, since you can expose a safe interface to the data structure.
[0] I'm not that great at Rust, so if there's some hidden cost to this beyond just the conversion methods themselves, someone please correct me.
Static Dispatch is very analogous to C++ templates (with the as of yet not included in the standard "Concepts" as Traits), so you get a copy of the function specialized for each type. (We get nicer error messages than templates in C++ because we require you to declare up front what methods you are expecting the type to have at function definition time instead of checking at specialization time that everything is defined.) There is no runtime cost to using a statically dispatched trait over a hand specialized version of the function.
The other method, boxed traits, is very similar to vtables. It has some runtime overhead, since the size of the time is not known, you must have a pointer to it (hence "boxed"). I think Rust currently uses fat pointers for this, that is a pair of pointers, one to the object, and the other to the vtable, since you can add new instances to types in other crates, so it'd be tricky to have a complete vtable for all methods of all the traits the type implements in one place.
impl From<T> for GeoBox
... outside the module/crate in which GeoBox is defined (as suggested at the end)?It's only where both the left and right-hand side are in external crates that would be a problem.