traits and associated-types - rust

I'm trying to implement something on Rust with traits and associated types. I'm not sure how to form my question with words, so I'll add a code snippet which will hopefully illustrate what I'm trying to do.
pub trait Person {}
pub trait Directory<P: Person> {
type Per = P;
fn get_person(&self) -> Self::Per;
}
pub trait Catalog {
type Per : Person;
type Dir : Directory<Self::Per>;
fn get_directory(&self) -> Self::Dir;
}
fn do_something<C>(catalog: C) where C: Catalog {
let directory : C::Dir = catalog.get_directory();
// let person : C::Per = directory.get_person();
// The code above fails with:
// error: mismatched types:
// expected `<C as Catalog>::Per`,
// found `<<C as Catalog>::Dir as Directory<<C as Catalog>::Per>>::Per`
// (expected trait `Catalog`,
// found trait `Directory`) [E0308]
let person = directory.get_person();
do_something_with_person(person);
}
fn do_something_with_person<P: Person>(p: P) {}
I would expect that the above code would compile, but it does not.
Instead, I get:
error: the trait `Person` is not implemented for the type `<<C as Catalog>::Dir as Directory<<C as Catalog>::Per>>::Per` [E0277]
Which, AFAICT, means that the compiler cannot determine that the person variable has the Person trait.
I'm using the following rustc version:
rustc 1.2.0-dev (a19ed8ad1 2015-06-18)
Am I missing something?

Here there's the correction:
pub trait Directory<P: Person> {
type Per : Person = P;
fn get_person(&self) -> Self::Per;
}
The type Per in Directory can be redefined in trait implementations. The compiler doesn't know if Self::Per (which is the re-defined Per in the implementation) implements the trait Person, so you have to bound it to implement Person.

Here’s the correct code:
pub trait Person {}
pub trait Directory {
type Person: Person;
fn get_person(&self) -> Self::Person;
}
pub trait Catalog {
type Dir: Directory;
fn get_directory(&self) -> Self::Dir;
}
fn do_something<C: Catalog>(catalog: C) {
let directory = catalog.get_directory();
let person = directory.get_person();
do_something_with_person(person);
}
fn do_something_with_person<P: Person>(p: P) {}
<P: Person> is generics syntax. Associated type definitions do not use generic syntax.
Go for the full name in general; don’t abbreviate it Per, leave it Person. It’s always qualified (Directory::Person, C::Person, &c.), so there’s no ambiguity. (Dir is an acknowledged short form of Directory, so there I guess either would be acceptable. I’d probably tend to go with Directory, however.)
There’s no need to have the associated type Person specified on Catalog, either; Self::Dir::Person will do.

Related

How to assign an impl trait in a struct?

Consider some struct (HiddenInaccessibleStruct) that is not accessible, but implements an API trait. The only way to obtain an object of this hidden type is by calling a function, that returns an opaque implementation of this type. Another struct owns some type, that makes use of this API trait. Right now, it seems not possible to assign this field in fn new(). The code below can also be found in rust playgrounds.
// -- public api
trait Bound {
fn call(&self) -> Self;
}
// this is not visible
#[derive(Default)]
struct HiddenInaccessibleStruct;
impl Bound for HiddenInaccessibleStruct {
fn call(&self) -> Self { }
}
// -- public api
pub fn load() -> impl Bound {
HiddenInaccessibleStruct::default()
}
struct Abc<T> where T : Bound {
field : T
}
impl<T> Abc<T> where T : Bound {
pub fn new() -> Self {
let field = load();
Abc {
field // this won't work, since `field` has an opaque type.
}
}
}
Update
The API trait Bound declares a function, that returns Self, hence it is not Sized.
There are two concepts in mid-air collision here: Universal types and existential types. An Abc<T> is a universal type and we, including Abc, can refer to whatever T actually is as T (simple as that). impl Trait-types are Rust's closest approach to existential types, where we only promise that such a type exists, but we can't refer to it (there is no T which holds the solution). This also means your constructor can't actually create a Abc<T>, because it can't decide what T is. Also see this article.
One solution is to kick the problem upstairs: Change the constructor to take a T from the outside, and pass the value into it:
impl<T> Abc<T>
where
T: Bound,
{
pub fn new(field: T) -> Self {
Abc { field }
}
}
fn main() {
let field = load();
let abc = Abc::new(field);
}
See this playground.
This works, but it only shifts the problem: The type of abc in main() is Abc<impl Bound>, which is (currently) impossible to write down. If you change the line to let abc: () = ..., the compiler will complain that you are trying to assign Abc<impl Bound> to (). If you try to comply with the advice and change the line to let abc: Abc<impl Bound> = ..., the compiler will complain that this type is invalid. So you have to leave the type of abc being implied. This brings some useability issues with Abc<impl Bound>, because you can't easily put values of that type into other structs etc.; basically, the existential type "infects" the outer type containing it.
impl Trait-types are mostly useful for immediate consumption, e.g. impl Iterator<Item=...>. In your case, with the aim apparently being to hide the type, you may get away with sealing Bound. In a more general case, it may be better to use dynamic dispatch (Box<dyn Bound>).

Rust subtyping/"runtime polymorphism"/using traits in Boxes

Consider the following dumbed-down example:
pub trait ThemePark<A, V>
where
A: Attraction,
V: Visitor,
{
fn create(square_size: u32, name: &str) -> Self;
fn get_attractions(&self) -> Vec<A>;
fn get_visitors(&self) -> Vec<V>;
}
pub trait Attraction {}
pub trait Visitor {}
A concrete ThemePark could have an arbitrary set of attractions, as well as visitors. They are implemented with structs:
struct ElderlyWhiteMale;
impl Visitor for ElderlyWhiteMale {}
A ThemePark is wrapped in some company's assets, like so:
pub struct Asset<'a> {
name: &str,
theme_park: Box<ThemePark<> + 'a> // <-- This won't compile, as ThemePark needs 2 type arguments
}
This begins my pain. I put ThemePark in a Box because I don't know the size of it at compile time. It could be wrapped around any kind of Attraction and Visitor.
ThemePark needs 2 type arguments, but I can't know them at compile-time. Somewhere in my code I read this from an external file and build a ThemePark accordingly.
The idea is, that at runtime I can create a ThemePark from an external source and then invoke the functions defined in the trait on it.
impl Asset {
fn init_and_query() -> () {
let theme_park: Box<ThemePark> = match external_file.get_theme_park_type {
ThemeParkType::FUN => unimplemented! {"There is no fun, yet!"},
ThemeParkType::SERIOUS => {
println!("Creating serious themepark");
SeriousThemePark::create(size /*...*/)
}
};
let attractions = theme_park.get_attractions();
// ... Do something with the attractions
}
}
pub enum ThemeParkType {
FUN,
SERIOUS,
}
I understand that I can't put the ThemePark as-is on the stack... it's size is unknown at compile time, so the compiler can't know what to allocate.
That's why I either use a reference & or wrap it in a Box like I do here.
I understand there is type erasure, meaning that I would get back only a ThemePark and not a SeriousThemePark, but that would suffice for the moment.
Am I using traits all wrong here? How would you go and fix that. Coming from Java/Scala/C++ I seem to be stuck too deep in existing thinking.
Rust polymorphism is very similar to C++ polymorphism in that regard; it features:
compile-time polymorphism, to parameterize an item with types known at compile-time,
run-time polymorphism, when the concrete types are not known at compile-time.
Rust uses trait to define an interface which is then used to both constrain compile-time type parameters and serve as base-class/interface for run-time polymorphism, which is perhaps where your confusion comes from, however both kinds of polymorphism are inherently different.
pub struct Asset<'a> {
name: &str,
theme_park: Box<ThemePark<> + 'a> // <-- This won't compile, as ThemePark needs 2 type arguments
}
Then you should NOT be using compile-time polymorphism, and instead define ThemePark as:
pub trait ThemePark {
fn create(square_size: u32, name: &str) -> Self;
fn get_attractions(&self) -> Vec<Box<Attraction>>;
fn get_visitors(&self) -> Vec<Box<Visitor>>;
}
By instantiating a ThemePark<A, V> you create a theme park which can only ever contain one type of attractions (it's all Haunted Houses here, no Flume Ride sorry!) and one type of visitors (only Elderly Guys in, no Elderly Ladies or Kids).

Use Trait as Vec Type

I'm new to Rust and have seen some examples of people using Box to allow pushing many types that implement a certain Trait onto a Vec. When using a Trait with Generics, I have run into an issue.
error[E0038]: the trait `collision::collision_detection::Collidable` cannot be made into an object
--> src/collision/collision_detection.rs:19:5
|
19 | collidables: Vec<Box<Collidable<P, M>>>,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `collision::collision_detection::Collidable` cannot be made into an object
|
= note: method `get_ncollide_shape` has generic type parameters
error: aborting due to previous error
error: Could not compile `game_proto`.
To learn more, run the command again with --verbose.
Here is my code
extern crate ncollide;
extern crate nalgebra as na;
use self::ncollide::shape::Shape;
use self::ncollide::math::Point;
use self::ncollide::math::Isometry;
use self::na::Isometry2;
pub trait Collidable<P: Point, M> {
fn get_ncollide_shape<T: Shape<P, M>>(&self) -> Box<T>;
fn get_isometry(&self) -> Isometry2<f64>;
}
pub struct CollisionRegistry<P, M>
where
P: Point,
M: Isometry<P>,
{
collidables: Vec<Box<Collidable<P, M>>>,
}
impl<P: Point, M: Isometry<P>> CollisionRegistry<P, M> {
pub fn new() -> Self {
let objs: Vec<Box<Collidable<P, M>>> = Vec::new();
CollisionRegistry { collidables: objs }
}
pub fn register<D>(&mut self, obj: Box<D>)
where
D: Collidable<P, M>,
{
self.collidables.push(obj);
}
}
I'm trying to use collidables as a list of heterogenous game objects that will give me ncollide compatible Shapes back to feed into the collision detection engine.
EDIT:
To clear up some confusion. I'm not trying to construct and return an instance of a Trait. I'm just trying to create a Vec that will allow any instance of the Collidable trait to be pushed onto it.
Rust is a compiled language, so when it compiles your code, it needs to know all of the information it might need to generate machine code.
When you say
trait MyTrait {
fn do_thing() -> Box<u32>;
}
struct Foo {
field: Box<MyTrait>
}
you are telling Rust that Foo will contain a box containing anything implementing MyTrait. By boxing the type, the compiler will erase any additional data about the data type that isn't covered by the trait. These trait objects are implemented as a set of data fields and a table of functions (called a vtable) that contains the functions exposed by the trait, so they can be called.
When you change
fn do_thing() -> Box<u32>;
to
fn do_thing<T>() -> Box<T>;
it may look similar, but the behavior is much different. Let's take a normal function example
fn do_thing<T>(val: T) { }
fn main() {
do_thing(true);
do_thing(45 as u32);
}
the compiler performs what is a called monomorphization, which means your code in the compiler becomes essentially
fn do_thing_bool(val: bool) { }
fn do_thing_num(val: u32) { }
fn main() {
do_thing_bool(true);
do_thing_num(45 as u32);
}
The key thing to realize is that you are asking it to do the same thing for your trait. The problem is that the compiler can't do it. The example above relies on knowing ahead of time that do_thing is called with a number in one case and a boolean in another, and it can know with 100% certainty that those are the only two ways the function is used.
With your code
trait MyTrait {
fn do_thing<T>() -> Box<T>;
}
the compiler does not know what types do_thing will be called with, so it has no way to generate functions you'd need to call. To do that, wherever you convert the struct implementing Collidable into a boxed object it would have to know every possible return type get_ncollide_shape could have, and that is not supported.
Other links for this:
Understanding Traits and Object Safety
https://www.reddit.com/r/rust/comments/3an132/how_to_wrap_a_trait_object_that_has_generic/

Static method in trait dynamic dispatch

Trying to get dynamic dispatch working in a trait static method but get a type must be known error.
I'm trying to achieve something like
F#
https://github.com/Thorium/SimpleCQRS-FSharp/blob/master/CommandSide/Domain.fs
C#
https://github.com/gregoryyoung/m-r/blob/master/SimpleCQRS/Domain.cs..
Is the only way to make the trait generic?
pub struct Aggregate<T: AggregateRoot>
{
pub id: Uuid,
agg: T,
changes: Vec<Box<Any>>
}
impl <T :AggregateRoot > Aggregate<T>
{
fn GetUncomittedChanges(&self) -> Vec<Box<Any>> { self.changes}
fn MarkChangesAsCommitted(&self) { self.changes.drain(..);}
}
trait AggregateRoot
{
fn new2() -> Self; //should be private
fn new(id: Uuid) -> Self;
fn LoadsFromHistory(changes : Vec<Box<Any>> ) -> Self
where Self: Sized
{
let newAgg = AggregateRoot::new2 ();
changes.iter().map( |e| newAgg.Apply(e) );
newAgg.MarkChangesAsCommitted();
newAgg
}
fn Apply<U: Any>(&self, arg: U) ;
fn GetId(&self) -> Uuid;
}
currently trying but gives 2 params expected 1 supplied.
Let's start with issues in how you asked the question, in the hopes that you will be able to ask better questions in the future. The complete error you are getting is:
<anon>:27:37: 27:52 error: the type of this value must be known in this context
<anon>:27 changes.iter().map( |e| newAgg.Apply(e) );
^~~~~~~~~~~~~~~
Note that the compiler error message shows you exactly which bit of code is at fault. It's useful to include that error when asking a question.
You've also included extraneous detail. For example, GetUncomittedChanges, id and GetId are all unused in your example. When solving a problem, you should produce an MCVE. This helps you understand the problem better and also allows people helping you to look at less code which usually results in faster turnaround.
Your code has a number of problems, but let's start at the first error:
let newAgg = AggregateRoot::new2 ();
This says "for any possible AggregateRoot, create a new one". Many concrete types can implement a trait (which is the point of traits), but the compiler needs to know how much space to allocate for a given instance. There might be a struct that takes 1 byte or 200 bytes; how much space needs to be allocated on the stack in this case?
To progress, you can use Self::new2 instead. That means to create a new instance of the current implementor.
The next error is
<anon>:20:16: 20:40 error: no method named `MarkChangesAsCommitted` found for type `Self` in the current scope
<anon>:20 newAgg.MarkChangesAsCommitted();
^~~~~~~~~~~~~~~~~~~~~~~~
You are calling a method on a concrete type from a trait implementation; this simply doesn't make any sense. What would happen if a bool implements this trait? It doesn't have a MarkChangesAsCommitted method. I don't know what you intended in this case, so I'll just delete it.
Now you get this error:
<anon>:19:9: 19:16 error: `changes` does not live long enough
<anon>:19 changes.iter().map( |e| newAgg.Apply(e) );
^~~~~~~
note: reference must be valid for the static lifetime...
<anon>:17:5: 21:6 note: ...but borrowed value is only valid for the scope of parameters for function at 17:4
That's because your method Apply expects to be given a type that implements Any. However, you are passing a &Box<Any>. Any has a lifetime bound of 'static, and that reference is not static. A straightforward change is to accept a reference to a type that implements Any:
fn Apply<U: Any>(&self, arg: &U);
Now that the code compiles, there's a number of stylistic issues to fix:
no space before :
no space after >
no space before (
no space inside ()
map should not be used for side effects
function and variable names are camel_case
most of the time, accept a &[T] instead of a Vec<T> as a function argument.
use "Egyptian" braces, except when you are using a where clause.
All together, your code looks like:
use std::any::Any;
struct Aggregate<T: AggregateRoot> {
agg: T,
changes: Vec<Box<Any>>
}
impl<T: AggregateRoot> Aggregate<T> {
fn mark_changes_as_committed(&self) { }
}
trait AggregateRoot {
fn new() -> Self;
fn load_from_history(changes: &[Box<Any>]) -> Self
where Self: Sized
{
let new_agg = Self::new();
for change in changes { new_agg.apply(change) }
new_agg
}
fn apply<U: Any>(&self, arg: &U);
}
fn main() {}
Is there a way to constrain the concrete types of the AggregateRoot to Aggregates so mark_changes can be called?
Not that I'm aware of. It sounds like you want to move mark_changes to the trait and force all implementors of the trait to implement it:
trait AggregateRoot {
fn load_from_history(changes: &[Box<Any>]) -> Self
where Self: Sized
{
let new_agg = Self::new();
for change in changes { new_agg.apply(change) }
new_agg.mark_changes_as_committed();
new_agg
}
fn mark_changes_as_committed(&self);
// ...
}

Lifetimes in traits

I'm currently diving into rust and writing a little math library.
The problem I like to solve is quite simple, I want to model fields and rings, but I can't get the lifetimes right.
here's the code:
ring.rs:
pub trait Ring {
fn characteristic() -> int;
fn is_unit(&self) -> bool;
fn is_field() -> bool;
}
field.rs:
use ring::Ring;
pub trait Field : Ring {
fn some_field_method() -> bool {
true
}
}
impl Ring for Field {
fn is_field() -> bool {
true
}
}
when compiling I get the following error:
/src/field.rs:9:15: 9:20 error: explicit lifetime bound required
/src/field.rs:9 impl Ring for Field {
^~~~~
I read the rust documentation, with the lifetime section and the rust-by-example section about it. The motivation behind lifetimes is obvious to me and I understand all the given examples. But here, I'm totally lost.
Btw: this is the minified version, I tried giving the Field a named lifetime, also the impl and Ring and various combinations of said.
May anyone explain what is happening here, or, if this is too specific, how to work with lifetimes and traits.
Thanks
You are trying to do some king of inheritance-like pattern with trait, which is not really how they work.
You can think of traits as somehow similar to interfaces provided by some languages : they are only a guaranty that your struct will provide some methods.
The syntax trait Foo : Bar does not mean that somehow the trait Foo is a superset of trait Bar, and that implementing Foo for a struct will implement Bar as well. It only states that trait Foo can only be implemented for structs already implementing trait Bar. You still need to implement both traits yourself.
Following your example, your approach would be something like this :
First, define the trait Ring:
pub trait Ring {
fn characteristic(&self) -> int;
fn is_unit(&self) -> bool;
fn is_field(&self) -> bool {
// Default value : not every Ring is a Field
false
}
}
Then, the trait Field, which requires the trait Ring
pub trait Field : Ring {
fn some_field_method(&self) -> bool {
// This is a default value as well
true
}
}
Then, your struct which will be both a Ring and a Field
struct MyField;
// First, implement Ring for MyField
impl Ring for MyField {
fn characteristic(&self) -> int {
2i
}
fn is_unit(&self) -> bool {
false
}
fn is_field(&self) -> bool {
// We override the default value : MyField is a Field
true
}
}
// Then we can implement Field for MyField
impl Field for MyField {
// Nothing here if we keep default implementation of some_field_method
}
Now, some explanations about this strange error with lifetimes.
When you wrote impl Ring for Field {...}, you where actually trying to implement the trait Ring for trait object Field.
A trait object is what you get when you use a reference to a struct like a reference to one of its traits, and they are quite peculiar to use, and require some playing with lifetimes.
However, in most situation you won't need to play with them, and classic generics will suffice.
You can have a look to my answer here, where I explain it with more details.

Resources