Is there a trait to go from Bound<T> to Bound<Struct<T>>

I'd like to know if there's a standard way to bind two similar structs together. For instance, I have something like:

struct Node<T> {
     value: T
}

And I like to convert a Bound<T> to a Bound<Node<T>>.

I can't do:

impl <T> From<Bound<T>> for Bound<Node<T>> { ... }

because neither From nor Bound are defined in my module. Yet many implementations of this and similar functions for other structs have a very common implementation pattern:

impl<T> Node<T> 
{
    fn from_bound<N>(bound: Bound<N>) -> Bound<Self> 
    where
       N: Into<Self>
    {
        match bound {
             Unbounded => Unbounded,
             Included(item) => Included(item.into()),
             Excluded(item) => Excluded(item.into()),
        }
    }
}

This implementation can be pasted in every impl block of any struct which is similar to Node and for which there's a proper implementation of From for the types you need.

Is there a way to make the struct itself generic?


Side note: You may have noticed I'm moving (or copying) the arguments in the from_ methods above. I'm fairly new to Rust, and most of the values I would put in a Bound implement Copy; so I didn't trouble myself trying to borrow the boundary.

There's no general mechanism for it, because the new struct could have a different size and different semantics.

If you use newtype pattern with #[repr(transparent)] then you can use transmute or cast pointers. Otherwise there's no other way than to take one type apart and construct the other.

BTW: moving doesn't imply copying. It rather means the opposite: moving of non-Copy types guarantees that it doesn't make a copy, and there's still only one instance.

Rust doesn't have syntax that is specific to copying vs passing by reference (e.g. moving Box<T> passes by reference, and &str is a 2-usize copyable struct). Owned/borrowed syntax is only about ownership.

Hi @kornel,

Thanks for your response. For the time being, I'm using a macro with the implementation whenever I need to define from_bound. I think this requires some sort higher rank kinds (a la Haskell); but I haven't thought about it yet.

On the side note:

Yes. I didn't mean it like that. I know moving is not the same as copying. I just meant that the argument being bound: Bound<T> callers may forfeit the right to use the value again, aka a move; but for Copy types the same syntax means a copy instead of a move.

I've seen that many generic methods use a borrow because, I think, is nicest for the callers.

What about making your own trait, and implementing it? You could do something like

trait FromBound {
    fn from_bound<N>(bound: Bound<N>) -> Bound<Self> 
    where
       N: Into<Self>
    {
        match bound {
             Unbounded => Unbounded,
             Included(item) => Included(item.into()),
             Excluded(item) => Excluded(item.into()),
        }
    }
}

With all the methods having a default implementation, you could then just write something like this for each node like this:

impl<T> FromBound for Node<T> {}

It isn't exactly the same, as this requires importing the FromBound trait to use from_bound, but it should be similar otherwise.

1 Like

That was my first thought when I saw the question. It sounds like you're looking for a generic way to map one type to another, almost the equivalent of Haskell's bind operator or higher kinded types.

Apart from a special case regarding Fn() trait sugar and lifetimes, I don't know if Rust has a way to abstract over this sort of thing. Instead, you'll find monad-like types manually implement these sorts of methods (see Option::and_then(), Result::and_then(), Iterator::map(), FutureExt::and_then(), etc.).

There's an RFC for higher ranked trait bounds, but this is more focused on streaming iterator-like mechanisms. I believe the type system internals are in a bit of flux at the moment, where they're trying to refactor code into libraries and switch over to chalk.

Side note:

It depends, but I would say that actually, in Rust, by-value is a better (as in simpler) default. In many languages they teach you to "pass by reference" because "it is more efficient" or whatnot. For a plethora of reasons (that I'm not going to go into here – but the lack of implicit cloning and the strongly value-oriented nature of the language are two of them), this is not really the case in Rust, and passing by value is a safe default – and often the only option if the callee needs ownership of the value.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.