Rust newcomer trying to get up to speed in regards to traits. Apologize in advance for the (most probably) very dumb question
Consider this toy example:
use num::{pow};
trait HasPower {
type Output;
fn power(&self, exp: usize) -> Self::Output;
}
struct NumWithPower<T> {
num: T
}
impl<T> HasPower for NumWithPower<T> {
type Output = T;
fn power(&self, exp : usize) -> Self::Output {
pow(self.num, exp) // error
}
}
fn main() {
let a = NumWithPower{num:5};
let b = NumWithPower{num:5i64};
let c = NumWithPower{num:5f64};
println!("{}", a.power(5));
println!("{}", b.power(5));
println!("{}", c.power(5));
}
The compiler is kindly hinting to me that the T does not implement the Clone and One traits:
error[E0277]: the trait bound `T: Clone` is not satisfied
error[E0277]: the trait bound `T: One` is not satisfied
Coming from C++, I would expect this error to come up at the time of using the specific power function with types that do not satisfy those traits (e.g. a String), while here it's erroring out at the definition stage, regardless of the use of the function. In fact, if I explicitly restricts the boundaries of T, the code compiles fine:
impl<T: One + Clone + Copy> HasPower for NumWithPower<T> {
type Output = T;
fn power(&self, exp : usize) -> Self::Output {
pow(self.num, exp)
}
}
Wouldn't the usage of the power call be enough to determine the boundaries of the T in this case? Am I missing some Rust gotcha?
You're missing that Rust has explicitly rejected the C++ template generics. Rust generics are fully typed at definition time, so that post-monomorphization inference and errors are (mostly) impossible. To the first approximation, you must explicitly specif generic bounds for any property or methods that you need, and you can't use anything which is not specified at definition time.
This means that if you call a generic method foo, then you must use at least as strong where-bounds on your function as on foo itself (or call it with concrete types).
Great! Ended up with this, which covers this basic usage:
use num::{pow, One, Zero};
// Trick to implement trait aliases without experimental
trait CopyableNumber: One + Zero + Clone + Copy {}
impl<T: One + Zero + Clone + Copy> CopyableNumber for T {}
trait HasPower {
type Output;
fn power(&self, exp: usize) -> Self::Output;
}
// Impl for generic number
impl<T: CopyableNumber> HasPower for T {
type Output = T;
fn power(&self, exp: usize) -> Self::Output {
pow(*self, exp)
}
}
struct MyNum<T: CopyableNumber> {
num: T
}
// Impl for custom struct
impl<T: CopyableNumber> HasPower for MyNum<T> {
type Output = T;
fn power(&self, exp : usize) -> Self::Output {
pow(self.num, exp) // errpr
}
}
fn main() {
// Generic numbers
let a = 5;
let b = 5i64;
let c = 5f64;4;
println!("{}", a.power(5));
println!("{}", b.power(5));
println!("{}", c.power(5));
// Custom struct
let a = MyNum{num:5};
let b = MyNum{num:5i64};
let c = MyNum{num:5f64};
println!("{}", a.power(5));
println!("{}", b.power(5));
println!("{}", c.power(5));
}