From\into trait with params


#1

regarding from\into traits.
they allow conversion between object, however what if i want the conversion to act differently according to different params?
example:
if i had a an Employee object and a Manager object, and i wanted to convert an Employee to convert to a Manager, in certain situations i’d like the Manager to have a people_manager variable set to true and sometimes false.
so what i’d like to do is something like:
let e = Employee {};
let m_manage_people = Manager::from(e, true) //for people manager
let m_dont_manage_people = Manager::from(e, false) //for not managing people

i’d like the from implementation to check params like this and build the result struct accordingly
is there any standard way to achieve this?


#2

You don’t always need a From/Into impl - normal associated functions (“constructors”) work just as well and you can give them meaningful names.

If you did want the From however, you could do impl From<(Employee, bool)> for Manager and use that bool to determine the flag status. Alternatively, you can define an enum to make this a bit more readable:

enum ManagesPeople {
   Yes,
   No,
}

impl From<(Employee, ManagesPeople)> for Manager { ... }

#3

awsome this was exactly whati was looking for


#4

Can I hijack this? I’ve tried implementing this, and I’ve hit a snag I don’t understand. Here’s what I started with: https://play.rust-lang.org/?gist=8141c972333cebb78fde5684a613c47b&version=stable&mode=debug&edition=2015, a very simple Enum with some From impls. Now I wanted to add a parameter, but the result doest not compile: https://play.rust-lang.org/?gist=914fe4d12b0432c1d423e2374583aa48&version=stable&mode=debug&edition=2015

I understand the error message, but it feels wrong. Note that the Decider enum does not implement Into<Target>, so I don’t really see the ambiguity here. Thanks for any help :slight_smile:


#5

looks like the compiler thinks you’ve implemented a certain typed From trait twice.

  1. impl From<(Decider, Vec)> for Target
    2.impl<K,V> From< (Decider, Vec<(K, V)>)> for Target

the reason being Vec(K,V) could be Vec if T=(K,V) and in that case there is a clash


#6

Well, it’s not really true, because if T=(K, V), there’s no impl for Into<Target> for T. Maybe the compiler doesn’t realise this? It’s a tad strange, because in my first example, it’s working. Unless I’m missing something…


#7

Well found out. I’d like to know the explanation for this case as well.


#8

impl From<(Decider, u8)> for Target, which you have, automatically gives you (Decider, u8): Into<Target>.


#9

But Decider itself does not implement Into<Target>, so there’s really just one impl that could apply for (Decider, Vec<(Decider,u8)> , namely the one for (Decider, Vec<T>).


#10

Is the fact that it’s not currently implemented taken into account when computing overlap when foreign traits are involved? My understanding was overlap is not permitted taking future changes into account as well (i.e. someone implementing Into<Target> for Decider) in those cases.

Things get more interesting when local traits are involved. If you define some local trait and add it as an additional bound on the From impls for the tuples, I think the compiler will take those into account and look at existing impls (because you control them in this case - the trait is local and the target type, Target, is local).

I do wish we didn’t have these types of overlap puzzles to solve though - reasoning about this stuff is, I’d say, one of the more challenging things about Rust.


#11

More pieces to the puzzle:

  • The error is present even without the (Decider, u8) impl.
  • The error disappears if you replace each (Decider, [whatever]) with (Decider, Decider, [whatever]).

From<T> for Target is isomorphic to Trait for T (and AFAIK the orphan rules don’t currently give any special treatment to Self, simply treating it as the zeroth type parameter), so I’m going to simplify things and focus on this example:


A simpler successful scenario:

pub trait Trait {}

impl<T> Trait for Vec<T>
where T: Trait {}

impl<K,V> Trait for Vec<(K, V)>
where K: Trait, V: Trait {}

A simpler broken scenario:

pub trait Trait {}

pub struct Struct;

impl<T> Trait for (Struct, Vec<T>)  // <-- changed
where T: Trait {}

impl<K,V> Trait for (Struct, Vec<(K, V)>)  // <--- changed
where K: Trait, V: Trait {}

By inspection, in both scenarios, overlap only occurs if there may exist K, V such that:

K: Trait,
V: Trait,
(K, V): Trait,

Notice my use of the word “may.” Negative reasoning in the trait system is tricky; there’s a decent blog post by aturon about it. Basically, it is not good enough merely for us to fail to construct a counterexample from our given impls; that only suffices in the “closed world” modality described in the blog post. We must also consider impls that could be written in downstream crates, or added to upstream crates.

It was at this point that I began writing up an analysis of how I thought the compiler reasoned about this, in particular using the orphan rules to prove that (K, V): Trait alone is unsatisfiable.

…but, uhhh…then I noticed that this example also fails:

An even simpler broken example:

pub trait Trait {}

pub struct Struct;

impl<T> Trait for Vec<T>
where T: Trait {}

impl<K,V> Trait for Vec<(K, V)>
where K: Trait, V: Trait {}

impl Trait for (Struct, u8) {} // <--- added

Applying simple logic, one can see that (K, V): Trait implies (K, V) = (Struct, u8) (due to the orphan rules), and that Struct: !Trait and u8: !Trait (again, due to the orphan rules). But the compiler does not reason this far.

tl;dr: The compiler’s methods of reasoning are a lot more limited than they may sometimes seem.


#12

There was some discussion about making tuples fundamental - that would lift the orphan aspect here. I wonder if the reason compiler doesn’t analyze that far is intentional in light of that, or completely independent of it.

I’d love to see an “official” spec of how overlap is supposed to be computed, and all the nuanced things that interplay there, detailed in one coherent place. I shiver at the thought of introducing coworkers to this side of Rust, partly because I don’t think I know all the little details and partly because of how I suspect they’ll react. Actually, I myself am frightened of it.