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

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

我认为这可以通过 traits 和 specialization 来实现,但是下面的代码:

#![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 的第二个实现不比第一个实现更“具体”,并且在当前的稳定版或夜间 Rust 中有什么方法可以让我的原始代码工作吗?

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

最佳答案

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

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

关于rust - 允许函数接受 `T` 或任何 `FnMut(T) -> T`,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38811099/

相关文章:

rust - 命名包含字符串 ".rs"的 crate 有问题吗?

rust - 为什么我得到 "cannot borrow ` arr[_ ]` as mutable more than once at a time"?

rust - 将本地字符串作为切片返回 (&str)

unit-testing - Cargo 未在顶级文件中运行测试

rust - `flat_map` 如何影响我的代码?

rust - 如何返回特征的实例?

rust - 这是 Rust 生命周期系统中的错误,还是我只是不理解生命周期

rust - 如何返回借用状态以便稍后使用的 Rust 闭包?

http - 如何同时使用 Rejection 和问号运算符来处理 Warp 中的错误?

rust - 如何捕获通过管道传输到 Rust 程序的进程的输出?