Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Allow a function to accept a `T` or any `FnMut(T) -> T`

Tags:

rust

My goal is to make the last 2 lines of this code compile and the last assertion to pass:

struct State {
    string: String
}

impl State {
    fn string<F: FnMut(String) -> String>(mut self, mut f: F) -> Self {
        self.string = f(self.string);
        self
    }
}

fn main() {
    let state = State { string: String::from("foo") };
    assert_eq!(state.string, "foo");
    let state = state.string(|old| old + "bar");
    assert_eq!(state.string, "foobar");
    // let state = state.string(String::from("baz"));
    // assert_eq!(state.string, "baz");
}

I thought this would be possible with traits and specialization, but the following code:

#![feature(specialization)]

trait Get<T> {
    fn get(self, old: T) -> T;
}

impl<T> Get<T> for T {
    default fn get(self, _: T) -> T {
        self
    }
}

impl<T, F> Get<T> for F where F: FnMut(T) -> T {
    fn get(mut self, old: T) -> T {
        self(old)
    }
}

struct State {
    string: String
}

impl State {
    fn string<G: Get<String>>(mut self, g: G) -> Self {
        self.string = g.get(self.string);
        self
    }
}

throws this error (live):

error[E0119]: conflicting implementations of trait `Get<_>`:
  --> <anon>:13:1
   |
13 | impl<T, F> Get<T> for F where F: FnMut(T) -> T {
   | ^
   |
note: conflicting implementation is here:
  --> <anon>:7:1
   |
7  | impl<T> Get<T> for T {
   | ^

error: aborting due to previous error

So my question is, why is the second impl of Get not more "specific" than the first one, and is there any way in current stable or nightly Rust to get my original code to work?

Edit: I know implementing a trait for just one type would work, but I want a generic solution for any type as I want to be able to use this for any arbitrary fields of a struct.

like image 275
Dogbert Avatar asked Aug 07 '16 05:08

Dogbert


People also ask

What is FnMut in Rust?

FnMut is implemented automatically by closures which take mutable references to captured variables, as well as all types that implement Fn , e.g., (safe) function pointers (since FnMut is a supertrait of Fn ). Additionally, for any type F that implements FnMut , &mut F implements FnMut , too.

How do you pass parameters in Rust?

Parameter values can be passed by reference by prefixing the variable name with an & . In the example given below, we have a variable no, which is initially 5. A reference to the variable no is passed to the mutate_no_to_zero() function. The function operates on the original variable.

How do you call a function in Rust?

We define a function in Rust by entering fn followed by a function name and a set of parentheses. The curly brackets tell the compiler where the function body begins and ends. We can call any function we've defined by entering its name followed by a set of parentheses.

What is FnOnce in Rust?

Use FnOnce as a bound when you want to accept a parameter of function-like type and only need to call it once. If you need to call the parameter repeatedly, use FnMut as a bound; if you also need it to not mutate state, use Fn .


2 Answers

For your concrete issue, you don't need specialization:

struct State {
    string: String,
}

impl State {
    fn string<F>(mut self, mut f: F) -> Self
        where F: Thing
    {
        self.string = f.thing(self.string);
        self
    }
}

trait Thing {
    fn thing(&mut self, s: String) -> String;
}

impl Thing for String {
    fn thing(&mut self, _s: String) -> String {
        self.clone()
    }
}

impl<F> Thing for F
    where F: FnMut(String) -> String
{
    fn thing(&mut self, s: String) -> String {
        (self)(s)
    }
}

fn main() {
    let state = State { string: String::from("foo") };
    assert_eq!(state.string, "foo");
    let state = state.string(|old| old + "bar");
    assert_eq!(state.string, "foobar");
    let state = state.string(String::from("baz"));
    assert_eq!(state.string, "baz");
}

You may either want to require FnOnce or implement the trait for a &str. Right now, the allocation of the String is not being used, causing a bit of inefficiency.

You could then implement the trait multiple times for the interesting types:

struct State {
    string: String,
    vec: Vec<u8>,
}

impl State {
    fn string<F>(mut self, mut f: F) -> Self
        where F: Thing<String>
    {
        self.string = f.thing(self.string);
        self
    }

    fn vec<F>(mut self, mut f: F) -> Self
        where F: Thing<Vec<u8>>
    {
        self.vec = f.thing(self.vec);
        self
    }
}

trait Thing<T> {
    fn thing(&mut self, s: T) -> T;
}

impl Thing<String> for String {
    fn thing(&mut self, _s: String) -> String {
        self.clone()
    }
}

impl<F> Thing<String> for F
    where F: FnMut(String) -> String
{
    fn thing(&mut self, s: String) -> String {
        (self)(s)
    }
}

impl Thing<Vec<u8>> for Vec<u8> {
    fn thing(&mut self, _s: Vec<u8>) -> Vec<u8> {
        self.clone()
    }
}

impl<F> Thing<Vec<u8>> for F
    where F: FnMut(Vec<u8>) -> Vec<u8>
{
    fn thing(&mut self, s: Vec<u8>) -> Vec<u8> {
        (self)(s)
    }
}

fn main() {
    let state = State { string: String::from("foo"), vec: vec![1] };

    assert_eq!(state.string, "foo");
    let state = state.string(|old| old + "bar");
    assert_eq!(state.string, "foobar");
    let state = state.string(String::from("baz"));
    assert_eq!(state.string, "baz");

    assert_eq!(state.vec, [1]);
    let state = state.vec(|mut old: Vec<u8>| {
        old.push(2);
        old
    });
    assert_eq!(state.vec, [1, 2]);
    let state = state.vec(vec![3]);
    assert_eq!(state.vec, [3]);
}

I believe that repetition could be handled by a macro:

macro_rules! thing {
    ($t: ty) => {
        impl Thing<$t> for $t {
            default fn thing(&mut self, _val: $t) -> $t {
                self.clone()
            }
        }

        impl<F> Thing<$t> for F
            where F: FnMut($t) -> $t
        {
            fn thing(&mut self, val: $t) -> $t {
                (self)(val)
            }
        }
    }
}

thing!(String);
thing!(Vec<u8>);
like image 197
Shepmaster Avatar answered Oct 12 '22 12:10

Shepmaster


Specialization doesn't work here because specialization only works for chains. That is, there exist functions that satisfy the impl

impl<T, F> Get<T> for F where F: FnMut(T) -> T

but not

impl<T> Get<T> for T

so the latter cannot specialize the former.

The simplest way to fix this is to just write a GetString trait instead of a Get<T> trait; that way you don't have to consider specialization on such malarkey at all.

like image 42
Veedrac Avatar answered Oct 12 '22 14:10

Veedrac