We can implement the traits in core::ops to define the behavior of operators for our types. The traits themselves are annotated with #[lang =...] attributes so the compiler knows which traits and operators belong together.
For example, the Add implementation for primitive types looks like this (macro manually expanded and simplified from here):
impl Add for i32 {
type Output = i32;
fn add(self, other: i32) -> i32 {
self + other
}
}
To my surprise, the implementation uses the + operator internally, which presumably calls self.add(other), resulting in an infinite recursion. Obviously, things do not happen like this because expressions like 3 + 4 (assuming no constant folding) work perfectly fine.
Now consider this naive implementation of the Add trait:
use std::ops::Add;
struct Foo;
impl Add for Foo {
type Output = Foo;
fn add(self, other: Foo) -> Foo {
self + other
}
}
fn main() {
let two_foo = Foo + Foo;
}
The compiler warns that function cannot return without recurring and running this program in Debug mode properly stops with fatal runtime error: stack overflow.
How does the compiler know how to add two numbers without falling into a recursive loophole?
How does the compiler know to add two numbers without falling into a recursive loophole?
Because the compiler is the compiler, and the compiler knows it doesn't need an Add implementation to add two numbers. If it's doing constant folding, it just adds them. If it's generating code, it tells LLVM to add them at runtime.
Those Add implementations aren't there to tell the compiler how to add numbers, they're to implement Add for numbers so that user code can add numbers via the Add trait just like any user-defined type. If those implementations didn't exist, then you wouldn't be able to add numbers in generic methods, because they wouldn't implement Add.
To put it another way: Add is what the compiler uses when it doesn't otherwise know how to add things. But it already knows how to add numbers, so it doesn't need them. They're provided for consistency with other types.
The implementations of Add that rely on the addition operator + in the end need to point to operations on primitives (eg. integers) and arithmetic operations on those are implemented using compiler built-ins.
What is more, the primitives themselves are compiler built-ins as well - note that you won't be able to find their sources in the std documentation.
The bottom line is that primitive types don't actually need the code provided by the implementations of Add and other artithmetic operators' traits at all - these functionalities are provided by the compiler's intrinsics. Their trait implementations are provided for the purposes of generics.
Related
In the trait Extendable below, I'd like to make app generic where it currently uses the concrete type i32.
At first blush, you'd think to use a generic type but doing so while keeping Extendable object safe isn't easy.
trait Isoextender {
type Input;
type Output;
fn forward(&self, v: Self::Input) -> Self::Output;
fn backward(&self, v: Self::Output) -> Self::Input;
}
trait Extendable {
type Item;
fn app(
&self,
v: &dyn Isoextender<Input = Self::Item, Output = i32>,
) -> Box<dyn Extendable<Item = i32>>;
}
There's a (really neat) type erasure trick (link) I can use with std::Any but then I'm losing type information.
This is one of those things that feels like it should be possible from my understanding of how Rust works but which simply might not be. I do see there's an issue with size. Clearly rust needs to know the size of the associated type Item, but I don't see how to solve with with references/pointers in a way that's both object safe and keeps the type information.
Is it the case that:
I am missing something and it's actually possible?
This is not possible today, but it may become possible in the future?
This is not likely to be possible ever?
Update:
So I suppose a part of the issue here is that I feel like the following is a generic function that has only 1 possible implementation. You should only have to compile this once.
fn pointer_identity<T>(v: &T) -> &T {
v
}
It feels like I should be able to put this function into a vtable, but still somehow express that it is generic. There are a whole class of easily identifiable functions that effectively act this way. Interactions between trait objects often act this way. It's all pointers to functions where the types don't matter except to be carried forward.
I found some document ion that may answer my question.
Currently, the compiler has two concepts surrounding Generic code, only one of which seems to be surfaced in the type system (link).
It sounds like this may be possible some day, but not currently expressible with Rust.
Monomorphization
The compiler stamps out a different copy of the code of a generic function for each concrete type needed.
Polymorphization
In addition to MIR optimizations, rustc attempts to determine when fewer copies of functions are necessary and avoid making those copies - known as "polymorphization".
As a result of polymorphization, items collected during monomorphization cannot be assumed to be monomorphic.
It is intended that polymorphization be extended to more advanced cases, such as where only the size/alignment of a generic parameter are required.
I would like to have all float comparisons done with float_cmp::approx_eq (for example), but keep using the equality comparison operator == for it. How do I achieve this?
impl PartialEq for f32 {
fn eq(&self, other: &Self) -> bool {
approx_eq!(f32, *self, *other)
}
}
Results in:
error[E0119]: conflicting implementations of trait `std::cmp::PartialEq` for type `f32`
error[E0117]: only traits defined in the current crate can be implemented for arbitrary types
This is not possible:
This trait implementation is in direct conflict with an existing implementation. There is no trait overriding mechanism. In the future you may be able to specialize a generic trait implementation, but this wouldn't be covered by that anyway.
Rust has "orphan rules" that govern what trait implementations you are allowed to define. In short, some part of the trait or type must be defined by the current crate. Neither PartialEq and f32 are defined by you, so you cannot create this implementation.
The approx_eq! macro from float_cmp expands to code that uses == internally, so such implementation if it were allowed would cause infinite recursion.
I don't think there is a way to accomplish this and I'm not sure I'd suggest using it even if there were. This would affect all cases where float comparison is done, even deep in your dependencies that could end up causing problems. And this leaves little option to do non-approximate equality even if you explicitly wanted to.
You should handle cases where you only want to consider approximate equality explicitly.
I wrote a program where I manipulated a lot of BigInt and BigUint values and perform some arithmetic operations.
I produced code where I frequently used BigInt::from(Xu8) because it is not possible to directly add numbers from different types (if I understand correctly).
I want to reduce the number of BigInt::from in my code. I thought about a function to "wrap" this, but I would need a function for each type I want to convert into BigInt/BigUint:
fn short_name(n: X) -> BigInt {
return BigInt::from(n)
}
Where X will be each type I want to convert.
I couldn't find any solution that is not in contradiction with the static typing philosophy of Rust.
I feel that I am missing something about traits, but I am not very comfortable with them, and I did not find a solution using them.
Am I trying to do something impossible in Rust? Am I missing an obvious solution?
To answer this part:
I produced code where I frequently used BigInt::from(Xu8) because it is not possible to directly add numbers from different types (if I understand correctly).
On the contrary, if you look at BigInt's documentation you'll see many impl Add:
impl<'a> Add<BigInt> for &'a u64
impl Add<u8> for BigInt
and so on. The first allows calling a_ref_to_u64 + a_bigint, the second a_bigint + an_u8 (and both set OutputType to be BigInt). You don't need to convert these types to BigInt before adding them! And if you want your method to handle any such type you just need an Add bound similar to the From bound in Frxstrem's answer. Of course if you want many such operations, From may end up more readable.
The From<T> trait (and the complementary Into<T> trait) is what is typically used to convert between types in Rust. In fact, the BigInt::from method comes from the From trait.
You can modify your short_name function into a generic function with a where clause to accept all types that BigInt can be converted from:
fn short_name<T>(n: T) -> BigInt // function with generic type T
where
BigInt: From<T>, // where BigInt implements the From<T> trait
{
BigInt::from(n)
}
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.
This question already has answers here:
Is there any way to restrict a generic type to one of several types?
(2 answers)
Closed 3 years ago.
We can restrict a type to one or more traits by where clause.
My questions are:
Is it possible to restrict a type to just primitive number types?
How?
No.
To use a parametric type, you need the trait to define the valid operations you want to call on it. So you need a trait (or more) with all the operations you want to call.
The “primitive” types in Rust are not special in any way. They define their operators via the traits from std::ops (though obviously using compiler intrinsics) just like any “non-primitive” numberic types.
In fact, the boundary between “primitive” and “non-primitive” numeric types is even somewhat blurred, since for targets that lack an FPU, the standard library may be implementing floating point types in code, and in Rust it can do it transparently to the user.
So really, there is no such thing as primitive number types. Number types are defined by providing whatever operators you need to call. So just restrict your type by the std::ops traits.
Since primitives are not trait types you cannot use as boundary to restrict a generic type. But you can directly implement for a specific type:
struct Struct<T>(T);
trait Printer {
fn print(&self);
}
impl Printer for Struct<i32> {
fn print(&self) {
println!("Printing for i32 value: {}", self.0);
}
}
fn main() {
let x = Struct(15_i32);
let _z = Struct(14.2_f64);
x.print();
//_z.print();//compile error
}
Playground
Alternatively you can use Borrow<S> trait as trick, you can restrict your generic parameter as like below : ( T: Borrow<S> means T can be borrowed as S ) .
impl<T> Printer for Struct<T>
where
T: Borrow<f64> + Debug,
{
fn print(&self) {
println!("Printing for f64 value: {:?}", self.0);
}
}
Playground
But since you can implement Borrow<f64> for any type, this kind of restriction may not be considered as strict.
Also just for the numeric primitives you can use traits from num-traits crate like ToPrimitive AsPrimitive
If you think about traits as compile time duck typing, then a better question would be: what are the exact traits you are looking for in a number? Most of the operations on them could be defined as trait constraints on your types, see: the standard operator traits.
Even if you would define a trait in an external crate and implement the behaviour for specific types only in that external crate, thinking that the trait implementation rules will help you there (i.e. a trait can only be implemented to a type if either the trait or the type or both are in your current crate) would still not limit anyone to implement your trait for their own types.
Therefore, I see no other option but to implement the behaviours without generics for each primitive number type. To avoid code duplication, I would probably use macros for this -- after all, if you think about it in some ways you would manually do what the compiler does while it monomorphises your generic code.
That being said I see no reason to limit behaviours to numbers but to certain traits and I would rely on them as I described it in the first paragraph.