Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I implement std::convert::From such that it does not consume its input?

I have managed to make the Rust type checker go into an infinite loop. A very similar program compiles with no trouble. Why does the program I want not compile?

To save your time and effort, I have made minimal versions of the two programs that isolate the problem. Of course, the minimal version is a pointless program. You'll have to use your imagination to see my motivation.

Success

Let me start with the version that works. The struct F<T> wraps a T. The type Target can be converted from an F<T> provided T can.

struct F<T>(T);

impl<T> From<F<T>> for Target where Target: From<T> {
    fn from(a: F<T>) -> Target {
        let b = Target::from(a.0);
        f(&b)
    }
}

Here's an example caller:

fn main() {
    let x = Target;
    let y = F(F(F(x)));
    let z = Target::from(y);
    println!("{:?}", z);
}

This runs and prints "Target".

Failure

The function f does not consume its argument. I would prefer it if the From conversion also did not consume its argument, because the type F<T> could be expensive or impossible to clone. I can write a custom trait FromRef that differs from std::convert::From by accepting an immutable borrow instead of an owned value:

trait FromRef<T> {
    fn from_ref(a: &T) -> Self;
}

Of course, I ultimately want to use From<&'a T>, but by defining my own trait I can ask my question more clearly, without messing around with lifetime parameters. (The behaviour of the type-checker is the same using From<&'a T>).

Here's my implementation:

impl<T> FromRef<F<T>> for Target where Target: FromRef<T> {
    fn from_ref(a: &F<T>) -> Target {
        let b = Target::from_ref(&a.0);
        f(&b)
    }
}

This compiles. However, the main() function doesn't:

fn main() {
    let x = Target;
    let y = F(F(F(x)));
    let z = Target::from_ref(y);
    println!("{:?}", z);
}

It gives a huge error message beginning:

error[E0275]: overflow evaluating the requirement `_: std::marker::Sized`
  --> <anon>:26:13
   |
26 |     let z = Target::from_ref(y);
   |             ^^^^^^^^^^^^^^^^
   |
   = note: consider adding a `#![recursion_limit="128"]` attribute to your crate
   = note: required because of the requirements on the impl of `FromRef<F<_>>` for `Target`
   = note: required because of the requirements on the impl of `FromRef<F<F<_>>>` for `Target`
   = note: required because of the requirements on the impl of `FromRef<F<F<F<_>>>>` for `Target`
etc...

What am I doing wrong?

Update

I've randomly fixed it!

The problem was that I forgot to implement FromRef<Target> for Target.

So I would now like to know: what was the compiler thinking? I still can't relate the problem to the error message.

like image 940
apt1002 Avatar asked Nov 20 '16 17:11

apt1002


1 Answers

You can't avoid consuming the input in the standard From/Into traits.

They are defined to always consume the input. Their definition specifies both input and output as owned types, with unrelated lifetimes, so you can't even "cheat" by trying to consume a reference.

  • If you're returning a reference, you can implement AsRef<T> instead. Or if your type is a thin wrapper/smart pointer, Deref<T>. You can provide methods as_foo()

  • If you're returning a new (owned) object, the convention is to provide to_foo() methods.

like image 140
Kornel Avatar answered Oct 27 '22 01:10

Kornel