同一特性的不同实现特性类型不兼容,因为基础类型可能不同.这意味着为同一特性的不同实现特性类型生成的机器代码可以是不同的.但有时铁 rust 代码本身是相同的.那么,如果没有dyn特性,对于相似但不兼容的类型,是否有可能避免重复和重用相同的代码呢?这是可以实现的吗?还是我提出了一些不合理的要求?我想编写第二个示例,但要像第一个一样工作.

fn foo_true() -> impl Foo {
    Bar
}

fn foo_false() -> impl Foo {
    Baz
}

trait Foo {
    fn foo(&self) {}
}

struct Bar;
impl Foo for Bar {}
struct Baz;
impl Foo for Baz {}

fn f(b: bool) {
    // solution 1: works, but code duplication
    {
        if b {
            foo_true().foo()
        } else {
            foo_false().foo()
        };
    }
    // solution 2: doesn't work, no code duplication
    {
        let v = if b { foo_true() } else { foo_false() }; // `if` and `else` have incompatible types
        v.foo();
    }
    // solution 3: works, no code duplication, but unnecessary perf hit
    {
        let v: Box<dyn Foo> = if b {
            Box::new(foo_true())
        } else {
            Box::new(foo_false())
        };
        v.foo();
    }
}

我看了一下 crate ,或者自己做了类似的事情,但它仍然需要代码复制,只是以一种更封装的方式.

另外,这只是一个简化的例子,在我的实际代码中,foo部分总是相同的,它不只是碰巧相同,我认为你永远不应该复制和粘贴.有时重复代码是可以的,而不是试图找到一种真正复杂的方法来完成它,只是让您在将来更改它,并且根本不需要它,但这不是那种情况.

推荐答案

我个人不同意对两个不同类型的单个函数调用属于"重复代码"的说法.

然而,我怀疑您的实际问题不是关于单个函数调用.但事实是,你必须复制粘贴if次.我怀疑您的真实代码是这样的,而实际问题是要复制粘贴的整个if块代码.

fn f(b: bool) {
    if b {
        foo_true().foo()
    } else {
        foo_false().foo()
    }

    if b {
        foo_true().foo()
    } else {
        foo_false().foo()
    }

    if b {
        foo_true().foo()
    } else {
        foo_false().foo()
    }
}

归根结底,您要处理两种不同的类型,因此需要在某个地方手动处理.或者,您可以使用Box和动态调度,但您说您不想这样做.

您还可以执行以下操作:

fn f(b: bool) {
    let (foo_true, foo_false) = (foo_true(), foo_false());
    let foo: &dyn Foo = if b { &foo_true } else { &foo_false };

    foo.foo();
    foo.foo();
    foo.foo();
}

如果是因为你想完全外推if块.然后你可以将逻辑移动到一个单独的函数中,该函数接受一个闭包:

fn with<T, F>(b: bool, f: F) -> T
where
    F: FnOnce(&dyn Foo) -> T,
{
    if b {
        f(&foo_true())
    } else {
        f(&foo_true())
    }
}

and separately 100 if needed.

然后,您只需要实际的单行:

fn f(b: bool) {
    with(b, |foo| foo.foo());

    with(b, |foo| foo.foo());

    with(b, |foo| foo.foo());
}

然而,第一种情况需要实例化BarBaz,而第二种情况需要连续多次实例化相同的Bar/Baz.


而你说你想避免Box岁.那么,您应该同样避免&dyn,因为两者都引入了动态调度.

如果您希望同时避免Box和动态调度,那么最好的方法是引入一个enum,它包含实现Foo的所有变量.

您甚至不必自己维护所有重复的代码.你可以使用enum_dispatch crate,为你生成所有这些.使用enum_dispatch crate应该是这样的:

use enum_dispatch::enum_dispatch;

#[enum_dispatch]
trait Foo {
    fn foo(&self) {}
}

#[enum_dispatch(Foo)]
enum AnyFoo {
    Bar(Bar),
    Baz(Baz),
}

现在,您可以将f()简化为以下公式:

fn f(b: bool) {
    let foo: AnyFoo = if b { Bar.into() } else { Baz.into() };

    foo.foo();
    foo.foo();
    foo.foo();
}

此外,还可以看看benchmarks for the enum approach.因为它显示了使用enumBox&dyn的基准结果.

Rust相关问答推荐

在Rust中显式装箱受生存期限制的转换闭包

从Type::new()调用函数

使用极点数据帧时,找不到枚举结果的方法lazy()

在Rust中声明和定义一个 struct 体有什么区别

如何在Rust中基于字符串 Select struct ?

如何使用reqwest进行异步请求?

装箱特性如何影响传递给它的参数的生命周期 ?(举一个非常具体的例子)

处理带有panic 的 Err 时,匹配臂具有不兼容的类型

为什么在 Allocator API 中 allocate() 使用 `[u8]` 而 deallocate 使用 `u8` ?

Rust 1.70 中未找到 Trait 实现

如何为整数切片定义一个带有额外函数的特性别名?

方法可以被误认为是标准特性方法

Rust: 目标成员属于哪个"目标家族"的列表是否存在?

在1.5n次比较中找到整数向量中的最大和次大整数

max(ctz(x), ctz(y)) 有更快的算法吗?

有没有办法隐式绑定 let/match 操作的成员?

如何在 Rust 中将 bson::Bson 转换为 Vec

从现有系列和 map 值创建新系列

当用作函数参数时,不强制执行与绑定的关联类型

守卫如何影响匹配语句?