I have a trait with some small methods, which are generally implemented as one-line wrappers around other methods that the implementing structs have. If I want to make sure that the trait method is inlined, should I place #[inline(always)] inside the trait definition, or inside the impl for each struct? I'd prefer to simply put it in the trait definition, but as far as I can tell that doesn't work.
What does inline mean?
When a compiler inlines a call, it copies the body of the function at the call site. Essentially, it's as if the code had been copy/pasted at each call site where it's inlined.
What does #[inline(always)] mean?
This instructs the compiler to perform inlining, always.
Normally, the compiler performs inlining when:
the body of the function is known
the set of heuristics estimate that this is a good trade-off (it might not be, though) which notably depends on the size of the function body
Why can I not specify #[inline(always)] on a trait method?
Because there is no body.
This may sounds trite, I know, however this is nonetheless true.
In Rust, traits may be used in two ways:
as bounds, for generic parameters
as runtime interfaces, aka trait objects
When used as a trait object, there is literally no body: the function to be called is determined at runtime!
Now, there are specific optimizations (devirtualizations) where the compiler attempts to divine or track the actual dynamic type of variables to be able to avoid the dynamic dispatch. I've even seen partial devirtualization in GCC where the compiler computes a likeliness of each type and creates an if ladder for the sufficiently likely one (if A { A::call(x); } else if B { B::call(x); } else { x.call(); }). However those are not guaranteed to succeed, of course.
So, what would be the semantics of #[inline(always)] on a virtual call? Should the compiler just ignore the attribute silently (uh!)?
It seems to me that what you are looking for is a new attribute (require(inline(always))?) to enforce specific constraints on the implementations of trait methods.
As far as I know, this does not exist yet.
Related
I was reading the question The trait `std::fmt::Write` is not implemented for `Stdout` when it should be where the asker noted that the rust documentation shows that the std::io::Write trait is implemented for both &Stdout and Stdout.
I don't understand why this is necessary or how you would use it. Isn't everything you define for Thing always implemented for &Thing? Why would you implement something for &Thing without implementing it for it's definition?
Isn't everything you define for Thing always implemented for &Thing?
No, an implementation for a type T will not automatically implement anything for &T. Now, sometimes blanket implementations can kick in, and if you pass a &&T to a function expecting a &T, then Rust will insert dereferences for you, but that does not mean the trait was implemented for &T, just that Rust helped you out a bit.
Why would you implement something for &Thing without implementing it for it's definition?
There's a very good example of that which we use all the time: String::from.
impl From<&str> for String {
fn from(value: &str) -> String {
...
}
}
From::<T>::from takes an argument, by value. No references or anything, just straight-up a value of type T. So we can never write a From<str> implementation for anything, since str is unsized and hence cannot be a function argument on its own. But it makes perfect sense to convert a &str to a String: that's just making an owned copy of the string.
Building on #Silvio-Mayolo's answer: in this particular case, there was originally just an implementation for Stdout; and the more-flexible implementation for &Stdout was added later, which provides a strict superset of functionality, notably allowing you to share the reference between threads. The original implementation can't be removed without breaking backwards compatibility with existing code using the by-value implementation (auto-deref isn't perfect, you would still need to go back and add an & in some situations).
The documentation at https://doc.rust-lang.org/std/convert/trait.From.html states
Note: This trait must not fail. If the conversion can fail, use TryFrom.
Suppose I have a From implementation thus:
impl From<SomeStruct> for http::Uri {
fn from(item: SomeStruct) -> http::Uri {
item.uri.parse::<http::Uri>() // can fail
}
}
Further suppose I am completely certain that item.uri.parse will succeed. Is it idiomatic to panic in this scenario? Say, with:
item.uri.parse::<http::Uri>().unwrap()
In this particular case, it appears there's no way to construct an HTTP URI at compile time: https://docs.rs/http/0.2.5/src/http/uri/mod.rs.html#117. In the real scenario .uri is an associated const, so I can test all used values parse. But it seems to me there could be other scenarios when the author is confident in the infallibility of a piece of code, particularly when that confidence can be encoded in tests, and would therefore prefer the ergonomics of From over TryFrom. The Rust compiler, typically quite strict, doesn't prevent this behaviour, though it seems it perhaps could. This makes me think this is a decision the author has been deliberately allowed to make. So the question is asking: what do people tend to do in this situation?
So in general, traits only enforce that the implementors adhere to the signatures and types as laid out in the trait. At least that's what the compiler enforces.
On top of that, there are certain contracts that traits are expected to adhere to just so that there's no weird surprises by those who work with these traits. These contracts aren't checked by the compiler; that would be quite difficult.
Nothing prevents you from implementing all a trait's methods but in way that's totally unrelated to what the trait is all about, like implementing the Display trait but then in the fmt method not actually bothering to use write! and instead, I don't know, delete the user's home directory.
Now back to your specific case. If your from method will not fail, provably so, then of course you can use .unwrap. The point of the cannot fail contract for the From trait is that those who rely on the From trait want to be able to assume that the conversion will go through every time. If you actually panic in your own implementation of from, it means the conversion sometimes doesn't go through, counter to the ideas and contracts in the From trait.
This question already has answers here:
Why are trait methods with generic type parameters object-unsafe?
(2 answers)
Closed 1 year ago.
Is this requirement really necessary for object safety or is it just an arbitrary limitation, enacted to make the compiler implementation simpler?
A method with type arguments is just a template for constructing multiple distinct methods with concrete types. It is known at compile time, which variants of the method are used. Therefore, in the context of a program, a typed method has the semantics of a finite collection of non-typed methods.
I would like to see if there are any mistakes in this reasoning.
I will take this opportunity to present withoutboat's nomenclature of Handshaking patterns, a set of ideas to reason about the decomposition of a functionality into two interconnected traits:
you want any type which implements trait Alpha to be composable with any type which implements trait Omega…
The example given is for serialization (although other use cases apply): a trait Serialize for types the values of which can be serialized (e.g. a data record type); and Serializer for types implementing a serialization format (e.g. a JSON serializer).
When the types of both can be statically inferred, designing the traits with the static handshake is ideal. The compiler will create only the necessary functions monomorphized against the types S needed by the program, while also providing the most room for optimizations.
trait Serialize {
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
where S: Serializer;
}
trait Serializer {
//...
fn serialize_map_value<S>(&mut self, state: &mut Self::MapState, value: &S)
-> Result<(), Self::Error>
where S: Serialize;
fn serialize_seq_elt<S>(&mut self, state: &mut Self::SeqState, elt: &S)
-> Result<(), Self::Error>;
where S: Serialize;
//...
}
However, it is established that these traits cannot do dynamic dispatching. This is because once the concrete type is erased from the receiving type, that trait object is bound to a fixed table of its trait implementation, one entry per method. With this design, the compiler is unable to reason with a method containing type parameters, because it cannot monomorphize over that implementation at compile time.
A method with type arguments is just a template for constructing multiple distinct methods with concrete types. It is known at compile time, which variants of the method are used. Therefore, in the context of a program, a typed method has the semantics of a finite collection of non-typed methods.
One may be led to think that all trait implementations available are known, and therefore one could revamp the concept of a trait object to create a virtual table with multiple "layers" for a generic method, thus being able to do a form of one-sided monomorphization of that trait object. However, this does not account for two things:
The number of implementations can be huge. Just look, for example, at how many types implement Read and Write in the standard library. The number of monomorphized implementations that would have to be made present in the binary would be the product of all known implementations against the known parameter types of a given call. In the example above, it is particularly unwieldy: serializing dynamic data records to JSON and TOML would mean that there would have to be Serialize.serialize method implementations for both JSON and TOML, for each serializable type, regardless of how many of these types are effectively serialized in practice. This without accounting the other side of the handshake.
This expansion can only be done when all possible implementations are known at compile time, which is not necessarily the case. While not entirely common, it is currently possible for a trait object to be created from a dynamically linked shared object. In this case, there is never a chance to expand the method calls of that trait object against the target compilation item. With this in mind, the virtual function table created by a trait implementation is expected to be independent from the existence of other types and from how it is used.
To conclude: This is a conceptual limitation that actually makes sense when digging deeper. It is definitely not arbitrary or applied lightly. Generic method calls in trait objects is too unlikely to ever be supported, and so consumers should instead rely on employing the right interface design for the task. Thinking of handshake patterns is one possible way to mind-map these designs.
See also:
What is the cited problem with using generic type parameters in trait objects?
The Rust Programming Language, section 17.2: Object Safety Is Required for Trait Objects
I have this code:
fn main() {
let p = Person;
let r = &p as &dyn Eatable;
Consumer::consume(r);
// Compile error
Consumer::consume_generic(r);
}
trait Eatable {}
struct Person;
impl Eatable for Person {}
struct Consumer;
impl Consumer {
fn consume(eatable: &dyn Eatable) {}
fn consume_generic<T: Eatable>(eatable: &T) {}
}
Error:
the size for values of type dyn Eatable cannot be known at
compilation time
I think it is strange. I have a method that literally takes a dyn Eatable and compiles fine, so that method knows somehow the size of Eatable. The generic method (consume_generic) will properly compile down for every used type for performance and the consume method will not.
So a few questions arise: why the compiler error? Are there things inside the body of the methods in which I can do something which I can not do in the other method? When should I prefer the one over the other?
Sidenote: I asked this question for the language Swift as well: Differences generic protocol type parameter vs direct protocol type. In Swift I get the same compile error but the underlying error is different: protocols/traits do not conform to themselves (because Swift protocols can holds initializers, static things etc. which makes it harder to generically reference them). I also tried it in Java, I believe the generic type is erased and it makes absolutely no difference.
The problem is not with the functions themselves, but with the trait bounds on types.
Every generic types in Rust has an implicit Sized bound: since this is correct in the majority of cases, it was decided not to force the developer to write this out every time. But, if you are using this type only behind some kind of reference, as you do here, you may want to lift this restriction by specifying T: ?Sized. If you add this, your code will compile fine:
impl Consumer {
fn consume(eatable: &dyn Eatable) {}
fn consume_generic<T: Eatable + ?Sized>(eatable: &T) {}
}
Playground as a proof
As for the other questions, the main difference is in static vs dynamic dispatch.
When you use the generic function (or the semantically equivalent impl Trait syntax), the function calls are dispatched statically. That is, for every type of argument you pass to the function, compiler generates the definition independently of others. This will likely result in more optimized code in most cases, but the drawbacks are possibly larger binary size and some limitations in API (e.g. you can't easily create a heterogeneous collection this way).
When you use dyn Trait syntax, you opt in for dynamic dispatch. The necessary data will be stored into the table attached to trait object, and the correct implementation for every trait method will be chosen at runtime. The consumer, however, needs to be compiled only once. This is usually slower, both due to the indirection and to the fact that individual optimizations are impossible, but more flexible.
As for the recommendations (note that this is an opinion, not the fact) - I'd say it's better to stick to generics whenever possible and only change it to trait objects if the goal is impossible to achieve otherwise.
I'm toying with the cgmath library. I have the following main.rs file:
extern crate cgmath;
use cgmath::vector::{Vector3, EuclideanVector};
fn main() {
let mypoint = Vector3 { x: 1f64, y: 1f64, z: 3f64 };
println!("The length of the vector is {}, and the length squared is {}", mypoint.length(), mypoint.length2());
}
In my use line, when I omit EuclideanVector, I am given the following compilation error:
type 'cgmath::vector::Vector3<f64>' does not implement any method in scope named 'length'
It appears that the Rust compiler cannot find the length() method unless I import one of the traits that Vector3 uses. Delving into the source code, it looks like the length method is defined within the EuclideanVector trait.
Intuitively, I should not need to import an trait to use a type that inherits said trait. Is there a technique to do so that I am missing? Is this a nuance specific to the cgmath library? Is this an idiomatic part of Rust that I should become accustomed to?
You're thinking of traits in terms of inheritance. It might make more sense if you think of a trait as a module that's overloadable with respect to the Self type. From this perspective, it makes sense that the trait has to be in scope in order for the compiler to know about its methods, just as a module has to be in scope in order to use it. A particular implication of this is that implementations can be declared alongside the trait they implement rather than the type they implement it for. In this situation, clearly if your code doesn't know about the trait, it can't use its methods.
Another motivation for the current behaviour is that multiple traits can define methods with the same name, and when you have such a conflict for traits implemented for the same type, you can no longer use method call syntax to access their methods. Instead, you have to use function call syntax in order to specify which trait the method is a member of (the trait acting as the module the method is in). If method call syntax used all traits in your program rather than just the ones in scope for method resolution, you'd end up with these conflicts much more often, since you'd have name conflicts with methods in traits that your code isn't actually using directly.
Strictly speaking, you don't have to use use. Alternatively:
(&mypoint as &cgmath::vector::EuclideanVector).length2()
Yes, this is how Rust works. You must always import a trait before you can use its methods. This is by design.
If you really don't want to import, you can call cgmath::vector::EuclideanVector::length(&mypoint).
(I don't know if this was possible when the question was asked.)