I want to implement an OOP approach in Rust. My base class BaseClass would look like this (but with more parameters):
struct BaseClass {
name: String,
}
impl BaseClass {
fn new(name: &str) -> Self {
Self{name: name.to_string()}
}
}
This base class has an associated trait that does the trick for inheritance:
trait BaseClassInterface {
fn as_base(&self) -> &BaseClass;
fn get_name(&self) -> &str {
&self.as_base().name
}
}
Now, I want to inherit from this base class and add extra stuff with generics:
struct MiddleClass<T> {
base: BaseClass,
value: T,
}
impl<T> MiddleClass<T> {
fn new(name: &str, value: T) -> Self {
Self{base: BaseClass::new(name), value}
}
}
Again, this is an "abstract" class. Users of my library will "inherit" from this middle class to define their structs. So let's do a trait for it:
trait MiddleClassInterface {
type Type;
fn as_middle(&self) -> &MiddleClass<Self::Type>;
fn get_value(&self) -> &Self::Type {
&self.as_middle().value
}
}
Now we implement the BaseClassInterface trait for the MiddleClassInterface trait and we achieved "class inheritance"!
impl<T> BaseClassInterface for dyn MiddleClassInterface<Type = T> {
fn as_base(&self) -> &BaseClass {
&self.as_middle().base
}
}
So now, users can implement their own versions of the MiddleClassInterface and use methods from the BaseClassInterface trait:
struct MyIntClass {
middle: MiddleClass<i32>,
}
impl MyIntClass {
fn new(name: &str, value: i32) -> Self {
Self{middle: MiddleClass::new(name, value)}
}
}
impl MiddleClassInterface for MyIntClass {
type Type = i32;
fn as_middle(&self) -> &MiddleClass<Self::Type> {
&self.middle
}
}
As MyIntClass implements the MiddleClassInterface, MyIntClass will implement the BaseClassInterface... or not?
Let's look at my main function:
fn main() {
let my_class = MyIntClass::new("my_class", 1);
println!("{}", my_class.get_name());
}
When compiling, I get the following error:
error[E0599]: no method named `get_name` found for struct `MyIntClass` in the current scope
--> src/main.rs:71:29
|
52 | struct MyIntClass {
| ----------------- method `get_name` not found for this
...
71 | println!("{}", my_class.get_name());
| ^^^^^^^^ method not found in `MyIntClass`
|
= help: items from traits can only be used if the trait is implemented and in scope
note: `BaseClassInterface` defines an item `get_name`, perhaps you need to implement it
--> src/main.rs:13:1
|
13 | trait BaseClassInterface {
| ^^^^^^^^^^^^^^^^^^^^^^^^
For more information about this error, try `rustc --explain E0599`.
MyIntClass implements the MiddleClassInterface<i32> class, which in turn implements the BaseClassInterface trait. So... why does MyIntClass not implement the BaseClassInterface indirectly?
Thanks in advance!
Playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=3aa8007b440b965aedf124a69dacc6f7
Your code will work if you change:
impl<T> BaseClassInterface for dyn MiddleClassInterface<Type = T> {
to:
impl<T> BaseClassInterface for T where T: MiddleClassInterface {
What you did above is implement BaseClassInterface for a trait object but such implementations are not considered for method lookup on concrete types. You would first have to coerce my_class into a trait for it to work:
(&my_class as &dyn MiddleClassInterface<Type=i32>).get_name())
Whereas the fix is to not implement BaseClassInterface for a trait object, but rather implement it for all types that implement the MiddleClassInterface. The difference being that MyIntClass will itself implement the base class and won't have to go through dynamic dispatch to call those methods.
Related
So I have a function. And I want to test it. It takes a struct as param. And the struct has some trait implemented. This trait has a long running io method. I don't want this io method to actually go fetch data, I want to mock this io method and just return the result. I am a little lost about how this can be done. Here is my try (not working)
struct TestStruct {
a: u32,
b: u32,
}
pub trait TestTrait {
fn some_long_running_io_method(&self) -> u32 {
156
}
}
fn important_func(a: TestStruct) {
println!("a: {}", a.some_long_running_io_method());
}
impl TestTrait for TestStruct {
fn some_long_running_io_method(&self) -> u32 {
self.a + self.b
}
}
#[cfg(test)]
mod tests {
use super::*;
use mockall::predicate::*;
use mockall::*;
#[cfg(test)]
mock! {
pub TestStruct {}
impl TestTrait for TestStruct {
fn some_long_running_io_method(&self) -> u32;
}
}
#[test]
fn test_important_func() {
let mut mock = MockTestStruct::new();
mock.expect_some_long_running_io_method()
.returning(|| 1);
important_func(mock);
}
}
I obviously get this error:
error[E0308]: mismatched types
--> src/test.rs:35:24
|
35 | important_func(mock);
| -------------- ^^^^ expected struct `TestStruct`, found struct `MockTestStruct`
| |
| arguments to this function are incorrect
How can I achieve mocking trait methods? 1) One way is to change function param and instead of accepting a concrete struct accept trait. And implement this trait on MockTestStruct. But then we have dynamic dispatching and it hurts the performance. I don't want a performance degrade just for the test. 2) I also tried reimplementing the Trait right where the test is, but conflicting implementations are not allowed in Rust. 3) Make function accept TestStruct or MockTestStruct? Probably not great way either.
Could you please tell me what is the idiomatic way to do it?
You can make your function, important_func a generic function. You can then use generic bounds to restrict the type to implementors of your trait.
Here is an example with your code:
struct TestStruct {
a: u32,
b: u32,
}
pub trait TestTrait {
fn some_long_running_io_method(&self) -> u32 {
156
}
}
// important_func can now use any type T which implements TestTrait,
// including your mock implementation!
fn important_func<T: TestTrait>(a: T) {
println!("a: {}", a.some_long_running_io_method());
}
impl TestTrait for TestStruct {
fn some_long_running_io_method(&self) -> u32 {
self.a + self.b
}
}
#[cfg(test)]
mod tests {
use super::*;
use mockall::predicate::*;
use mockall::*;
#[cfg(test)]
mock! {
pub TestStruct {}
impl TestTrait for TestStruct {
fn some_long_running_io_method(&self) -> u32;
}
}
#[test]
fn test_important_func() {
let mut mock = MockTestStruct::new();
mock.expect_some_long_running_io_method().returning(|| 1);
important_func(mock);
}
}
I'd like to define a trait with many methods:
pub trait DataSetT{
fn numeric_op_1(&self){...};
fn numeric_op_2(&self)->f64{...};
...
fn io_op_1(&self) {..};
fn io_op_2(&self) -> DataFrame {...};
...
}
Now, if I define all these methods in the same file it would get humongous.
For the sake of clean and visibile code, I'd like to split these definitions across different files/modules.
For example numeric operatins would live in:
src/numerics.rs
And io operations would live in
src/io.rs
Same thing with implementing this trait for a Struct (overriding the default trait behaviour).
As soon as I try doing that, I either get not all trait items implemented or confilicting definitions.
What is the best practice solution in this kind of situtation?
Without macro you should not be able to split a trait definition over different modules. Where you write trait MyTrait { .. } you need to define it.
But you can define multiple traits and have a super trait, like this:
// src/ops/a.rs
pub trait OpA {
fn op_a1(&self);
fn op_a2(&self) -> f64;
}
// src/ops/b.rs
pub trait OpB {
fn op_b1(&self);
fn op_b2(&self);
}
// src/ops/mod.rs
pub trait Op: OpA + OpB {}
// src/ops_impl/mod.rs
struct MyOp {}
impl Op for MyOp {}
// src/ops_impl/a.rs
impl OpA for MyOp {
fn op_a1(&self) {}
fn op_a2(&self) -> f64 {
42.0
}
}
// src/ops_impl/b.rs
impl OpB for MyOp {
fn op_b1(&self) {}
fn op_b2(&self) {}
}
Editor's note: This code example is from a version of Rust prior to 1.0 and is not syntactically valid Rust 1.0 code. Updated versions of this code produce different errors, but the answers still contain valuable information.
It seems like we cannot test for equality in the following case. Why is this? Is there a workaround? (I am using Rust 0.11).
trait A: PartialEq {}
#[deriving(PartialEq)]
enum T {Ta, Tb}
impl A for T {}
fn main() {
assert!(Ta == Ta);
assert!(Ta != Tb);
assert!(some_fn(&Ta, &Ta));
assert!(!some_fn(&Ta, &Tb));
}
fn some_fn(an_a: &A, another_a: &A) -> bool {
an_a == another_a
// ERROR ^~~~~~~~~~~~ binary operation `==` cannot be applied to type `&A`
}
fn another_fn(an_a: &A + PartialEq, another_a: &A + PartialEq) -> bool {
// ERROR: ^~~~~~~~~ only the builtin traits can be used as closure or object bounds
an_a == another_a
}
With help from Vladimir Matveev, I figured out how to use Any to downcast my trait to a concrete type and test the resulting value for equality:
// `Any` allows us to do dynamic typecasting.
use std::any::Any;
trait A {
// An &Any can be cast to a reference to a concrete type.
fn as_any(&self) -> &dyn Any;
// Perform the test.
fn equals_a(&self, _: &dyn A) -> bool;
}
#[derive(Debug, PartialEq)]
enum T {
Ta,
Tb,
}
// Implement A for all 'static types implementing PartialEq.
impl<S: 'static + PartialEq> A for S {
fn as_any(&self) -> &dyn Any {
self
}
fn equals_a(&self, other: &dyn A) -> bool {
// Do a type-safe casting. If the types are different,
// return false, otherwise test the values for equality.
other
.as_any()
.downcast_ref::<S>()
.map_or(false, |a| self == a)
}
}
fn main() {
assert_eq!(T::Ta, T::Ta);
assert_ne!(T::Ta, T::Tb);
assert!(some_fn(&T::Ta, &T::Ta));
assert!(!some_fn(&T::Ta, &T::Tb));
}
fn some_fn(an_a: &dyn A, another_a: &dyn A) -> bool {
// It works!
an_a.equals_a(another_a)
}
Here is the definition of the PartialEq trait:
pub trait PartialEq<Rhs = Self>
where
Rhs: ?Sized,
{
fn eq(&self, other: &Rhs) -> bool;
fn ne(&self, other: &Rhs) -> bool { ... }
}
Note the Self parameter type. This means that eq() and ne() methods accept a parameter of the same type as implementor. For example:
impl PartialEq for i32 {
fn eq(&self, other: &i32) -> bool { ... }
}
impl PartialEq for String {
fn eq(&self, other: &String) -> bool { ... }
}
Note how type of other changes to reflect the type PartialEq is implemented for.
This is the problem. In trait objects, the actual type is erased and unavailable at runtime. This means that it is impossible to obtain a reference to a concrete type from a trait object; in particular, you can't go from &A to &T in your example.
This means that it is impossible to call methods accepting or returning the Self type on trait objects. Indeed, these methods always require a concrete type, but if you have only a trait object, there is no concrete type, and there is no way such method could work in any sensible way.
In certain cases of trait objects, you wish to compare them based on some properties exposed via the trait. You can achieve this by implementing methods on the trait type itself:
trait A {
fn id(&self) -> i32;
}
impl PartialEq for dyn A + '_ {
fn eq(&self, other: &Self) -> bool {
self.id() == other.id()
}
}
impl Eq for dyn A + '_ {}
fn some_fn(an_a: &dyn A, another_a: &dyn A) -> bool {
an_a == another_a
}
This doesn't directly address the original case which wants to delegate back to the implementation of PartialEq of the underlying type, but you can combine the existing solution:
impl PartialEq for dyn A + '_ {
fn eq(&self, other: &Self) -> bool {
self.equals_a(other)
}
}
See also:
Why would I implement methods on a trait instead of as part of the trait?
Rust doesn't seem to distinguish between different implementations of a trait only if they differ by an associated type.
How can I implement a method on all kinds of collections/iterators, but have specific implementations for each concrete type they contain?
error: conflicting implementations for trait Foo [E0119]
The code:
trait Foo { fn foo(self); }
impl<T> Foo for T
where T: IntoIterator<Item=u32>
{
fn foo(self) {
self.into_iter();
}
}
impl<T> Foo for T
where T: IntoIterator<Item=u16>
{
fn foo(self) {
self.into_iter();
}
}
fn main() {
vec![0u32].foo();
vec![0u16].foo();
}
You can not do the generic form directly, which is issue #20400. You'll have to introduce either a trait that can be used as a bound on T::Item to merge the two implementations, or wrapper types. E.g. the first one might look like:
trait FooIterItem {
// behaviours needed for Foo impl
}
impl FooIterItem for u32 { ... }
impl FooIterItem for u16 { ... }
impl<T> Foo for T
where T: IntoIterator, T::Item: FooIterItem
{
...
}
I have the following types:
trait Monster {
fn attack(&self);
fn new(int) -> Self;
}
struct CookiesMonster {
cookies: int,
hungry_level: int,
}
impl Monster for CookiesMonster {
fn new(i: int) -> CookiesMonster {
CookiesMonster { cookies: i, hungry_level: i + 1 }
}
fn attack(&self) {
println!("I have {:d} cookies!!", self.cookies)
}
}
struct Dummy {
count: int
}
impl Dummy {
fn new(i: int) -> Dummy {
Dummy { count: i }
}
}
Now, this works:
let monster: CookiesMonster = Monster::new(10);
let dummy = Dummy::new(10);
But this doesn't:
let monster = CookiesMonster::new(10);
Why can't I call the new method directly on the CookiesMonster type?
Because that's how traits work at the moment. Static methods in traits must be called on the trait, and not on the implementor of the trait.
Note that calling the methods on the trait instead of on the type implementing the trait allows cases like this to be unambiguous: Consider if you added the following code to your example:
trait Newable {
fn new(int) -> Self;
}
impl Newable for CookiesMonster {
fn new(i: int) -> CookiesMonster {
CookiesMonster { cookies: i, hungry_level: 0 }
}
}
In this context, Monster::new still works, but CookiesMonster::new would be ambiguous.
(In this example, it figures how which implementation of the trait to use based on type inference. A generalized syntax such as Trait::<for Type>::static_method has been discussed as a way to explicitly write down one's intentions, but I am not sure how far along that is.)
Update circa 15 July 2014: the "Unified Function Call Syntax" proposal tracks the work alluded to in the previous paragraph. See Rust RFC PR 132. My understanding is that UFCS as described in that RFC would actually allow for you to write CookiesMonster::new when Monster is the only trait in scope that both (1.) provides the new method and (2.) is unambiguously implemented for CookiesMonster.