特征的静态工厂方法
Static factory method for trait
我刚学 Rust,所以可能我只是没有正确理解一些概念。
我有一些实现的特点:
trait Abstract {
fn name(&self) -> &str;
}
struct Foo {}
struct Bar {}
struct Baz {}
impl Abstract for Foo {
fn name(&self) -> &str { "foo" }
}
impl Abstract for Bar {
fn name(&self) -> &str { "bar" }
}
impl Abstract for Baz {
fn name(&self) -> &str { "baz" }
}
我想向这个特征添加一个静态方法来创建一些标准的名称实现:
trait Abstract {
fn new(name: &str) -> Self {
match name {
"foo" => Foo{},
"bar" => Bar{},
"baz" => Baz{},
}
}
fn name(&self) -> &str;
}
但此代码无法编译,因为:
6 | fn new(name: &str) -> Self {
| ^^^^ doesn't have a size known at compile-time
我尝试使用 return fn new(name: &str) -> impl Abstract
和
fn new<T: Abstract>(name: &str) -> T
- 但这些变体不适用于其他错误。
在 Rust 中为特征创建工厂方法的正确方法是什么?
fn new<T: Abstract>(…) -> T
意味着:new
将 return 一些实现 Abstract
的具体 T
,并且无论调用 new
可以决定哪个。
fn new() -> impl Abstract
表示:new
将 return 选择实现抽象的具体类型,但无论调用什么 new
都不知道具体类型。 (但它仍然必须是在编译时决定的某个单一具体类型,它不能是“可能 Foo
或可能 Baz
”)
如果你真的想要这个,你可以用一个免费的函数来做你的构建:
trait Abstract {
fn name(&self) -> &str;
}
fn new_abstract(name: &str) -> Box<dyn Abstract> {
match name {
"foo" => Box::new(Foo{}),
"bar" => Box::new(Bar{}),
"baz" => Box::new(Baz{}),
_ => panic!("better not")
}
}
new
函数不能成为 Abstract
的一部分,因为 object safety 会给您带来麻烦。本质上,如果你想 return 某个特征的 Box<dyn …>
,特征上的所有方法都必须在动态调度中可用,但 new
不是这样的方法。
总之,这不是个好主意。将单独的 new
函数添加到 Foo
、Bar
和 Baz
是 Rust 的方法。
在 Rust 中,每个变量都必须是单一的特定类型。这与 OO 语言不同,其中变量可以具有类型 Foo
,这意味着变量包含 Foo
或 Foo
.
的任何子类
Rust 不支持这个。如果变量的类型为 Foo
,它 必须 包含 Foo
(忽略任何不安全的问题)。
Rust 也不支持使用特征作为类型(没有 dyn
关键字)。
在您的示例中,您有:
trait Abstract {
fn new(name: &str) -> Self {
// ...
}
}
这里的 return 类型 Self
的意思是“无论这个特性是在什么类型上实现的”。然而,通过在 trait 定义中提供一个主体,你提供了一个默认实现,所以这个函数理论上应该适用于任何类型,因此编译器没有关于真正具体类型的信息 Self
,因此Sized
没有满足它的限制(这在实践中是非常严格的,可能不是你想要的)。
如果您想要一个接受字符串和 return 的“实现 Abstract
的某种类型 T
”的函数,您可以使用“特征对象”,它看起来大致像:
// outside trait definition
fn new_abstract(name: &str) -> Box<dyn Abstract> { // dyn keyword opts into dynamic dispatch with vtables
match name {
"foo" => Box::new(Foo {}),
// ...
}
}
但是,我要警告不要使用这种模式。动态分派有一些运行时开销,并阻止了许多编译时优化。相反,可能有一种更“生锈”的方式来做,但如果没有更多的上下文就很难说。
一般来说,根据字符串的值构造类型有点反模式。
您可以考虑使用 enum
而不是动态调度它:
trait Abstract {
fn name(&self) -> &str;
fn new(name: &str) -> Option<AbstractVariant> {
match name {
"foo" => Some(AbstractVariant::Foo(Foo {})),
"bar" => Some(AbstractVariant::Bar(Bar {})),
"baz" => Some(AbstractVariant::Baz(Baz {})),
_ => None,
}
}
}
enum AbstractVariant {
Foo(Foo),
Bar(Bar),
Baz(Baz),
}
我刚学 Rust,所以可能我只是没有正确理解一些概念。
我有一些实现的特点:
trait Abstract {
fn name(&self) -> &str;
}
struct Foo {}
struct Bar {}
struct Baz {}
impl Abstract for Foo {
fn name(&self) -> &str { "foo" }
}
impl Abstract for Bar {
fn name(&self) -> &str { "bar" }
}
impl Abstract for Baz {
fn name(&self) -> &str { "baz" }
}
我想向这个特征添加一个静态方法来创建一些标准的名称实现:
trait Abstract {
fn new(name: &str) -> Self {
match name {
"foo" => Foo{},
"bar" => Bar{},
"baz" => Baz{},
}
}
fn name(&self) -> &str;
}
但此代码无法编译,因为:
6 | fn new(name: &str) -> Self {
| ^^^^ doesn't have a size known at compile-time
我尝试使用 return fn new(name: &str) -> impl Abstract
和
fn new<T: Abstract>(name: &str) -> T
- 但这些变体不适用于其他错误。
在 Rust 中为特征创建工厂方法的正确方法是什么?
fn new<T: Abstract>(…) -> T
意味着:new
将 return 一些实现Abstract
的具体T
,并且无论调用new
可以决定哪个。fn new() -> impl Abstract
表示:new
将 return 选择实现抽象的具体类型,但无论调用什么new
都不知道具体类型。 (但它仍然必须是在编译时决定的某个单一具体类型,它不能是“可能Foo
或可能Baz
”)
如果你真的想要这个,你可以用一个免费的函数来做你的构建:
trait Abstract {
fn name(&self) -> &str;
}
fn new_abstract(name: &str) -> Box<dyn Abstract> {
match name {
"foo" => Box::new(Foo{}),
"bar" => Box::new(Bar{}),
"baz" => Box::new(Baz{}),
_ => panic!("better not")
}
}
new
函数不能成为 Abstract
的一部分,因为 object safety 会给您带来麻烦。本质上,如果你想 return 某个特征的 Box<dyn …>
,特征上的所有方法都必须在动态调度中可用,但 new
不是这样的方法。
总之,这不是个好主意。将单独的 new
函数添加到 Foo
、Bar
和 Baz
是 Rust 的方法。
在 Rust 中,每个变量都必须是单一的特定类型。这与 OO 语言不同,其中变量可以具有类型 Foo
,这意味着变量包含 Foo
或 Foo
.
Rust 不支持这个。如果变量的类型为 Foo
,它 必须 包含 Foo
(忽略任何不安全的问题)。
Rust 也不支持使用特征作为类型(没有 dyn
关键字)。
在您的示例中,您有:
trait Abstract {
fn new(name: &str) -> Self {
// ...
}
}
这里的 return 类型 Self
的意思是“无论这个特性是在什么类型上实现的”。然而,通过在 trait 定义中提供一个主体,你提供了一个默认实现,所以这个函数理论上应该适用于任何类型,因此编译器没有关于真正具体类型的信息 Self
,因此Sized
没有满足它的限制(这在实践中是非常严格的,可能不是你想要的)。
如果您想要一个接受字符串和 return 的“实现 Abstract
的某种类型 T
”的函数,您可以使用“特征对象”,它看起来大致像:
// outside trait definition
fn new_abstract(name: &str) -> Box<dyn Abstract> { // dyn keyword opts into dynamic dispatch with vtables
match name {
"foo" => Box::new(Foo {}),
// ...
}
}
但是,我要警告不要使用这种模式。动态分派有一些运行时开销,并阻止了许多编译时优化。相反,可能有一种更“生锈”的方式来做,但如果没有更多的上下文就很难说。
一般来说,根据字符串的值构造类型有点反模式。
您可以考虑使用 enum
而不是动态调度它:
trait Abstract {
fn name(&self) -> &str;
fn new(name: &str) -> Option<AbstractVariant> {
match name {
"foo" => Some(AbstractVariant::Foo(Foo {})),
"bar" => Some(AbstractVariant::Bar(Bar {})),
"baz" => Some(AbstractVariant::Baz(Baz {})),
_ => None,
}
}
}
enum AbstractVariant {
Foo(Foo),
Bar(Bar),
Baz(Baz),
}