允许函数接受`T`或任何`FnMut(T) - > T`

时间:2016-08-07 05:11:50

标签: rust

我的目标是让这段代码的最后两行编译,最后一个断言传递:

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");
}

我认为这对于特征和专业化是可能的,但是以下代码:

#![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
    }
}

抛出此错误(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

所以我的问题是,为什么Get的第二个impl不是比第一个更具“特定”,并且在当前稳定或每晚Rust中有什么方法可以使我的原始代码工作?

编辑:我知道只为一种类型实现特征是可行的,但我想要任何类型的通用解决方案,因为我希望能够将它用于结构的任意字段。

2 个答案:

答案 0 :(得分:6)

对于您的具体问题,您不需要专业化:

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");
}

您可能要求FnOnce或实施&str的特征。现在,String的分配没有被使用,导致一些效率低下。

然后,您可以多次为有趣的类型实现特征:

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]);
}

我相信重复可以由宏处理:

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>);

答案 1 :(得分:4)

专业化在这里不起作用,因为专业化仅适用于链。也就是说,存在满足impl

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

但不是

impl<T> Get<T> for T

所以后者不能专门化前者。

解决此问题的最简单方法是只编写GetString特征而不是Get<T>特征;这样你就不必考虑对这种malarkey的专业化了。