【发布时间】:2020-04-20 15:05:32
【问题描述】:
给定一个依赖于泛型类型参数的结构,我们能否定义一个其实现依赖于该类型的关联函数?
我想将结构传递给例程,但相关函数的计算方式取决于内部类型。这个例程也依赖于结构中的成员,所以我宁愿不要把所有东西都移到一个 trait 中。
例如,以下代码尝试根据所涉及的类型定义不同的printme 函数:
// Trait locked to a type
trait MyF64 {}
impl MyF64 for f64 {}
trait MyU32 {}
impl MyU32 for u32 {}
// Some kind of struct
struct Foo<T> {
t: T,
}
// Implementation for f64
impl<T> Foo<T>
where
T: MyF64,
{
fn printme(&self) {
println!("In a f64: {}", self.t);
}
}
// Implementation for u32
impl<T> Foo<T>
where
T: MyU32,
{
fn printme(&self) {
println!("In a u32: {}", self.t);
}
}
// Takes a foo
fn foo<T>(x: Foo<T>) {
foo.printme();
}
fn main() {
// Try both cases
foo(Foo { t: 1.2 });
foo(Foo { t: 12 });
}
这会导致编译器错误:
error[E0592]: duplicate definitions with name `printme`
--> src/main.rs:17:5
|
17 | / fn printme(&self) {
18 | | println!("In a f64: {}", self.t);
19 | | }
| |_____^ duplicate definitions for `printme`
...
27 | / fn printme(&self) {
28 | | println!("In a u32: {}", self.t);
29 | | }
| |_____- other definition for `printme`
如果我将printme 的定义移到另一个特征中,我们会遇到类似但不同的问题
// Trait locked to a type
trait MyF64 {}
impl MyF64 for f64 {}
trait MyU32 {}
impl MyU32 for u32 {}
// Some kind of struct
struct Foo<T> {
t: T,
}
// Trait for Foo
trait FooTrait {
fn printme(&self);
}
// Implementation for f64
impl<T> FooTrait for Foo<T>
where
T: MyF64,
{
fn printme(&self) {
println!("In a f64: {}", self.t);
}
}
// Implementation for u32
impl<T> FooTrait for Foo<T>
where
T: MyU32,
{
fn printme(&self) {
println!("In a u32: {}", self.t);
}
}
// Takes a foo
fn foo<T>(x: Foo<T>)
where
Foo<T>: FooTrait,
{
foo.printme();
}
fn main() {
// Try both cases
foo(Foo { t: 1.2 });
foo(Foo { t: 12 });
}
这会导致编译器错误:
error[E0119]: conflicting implementations of trait `FooTrait` for type `Foo<_>`:
--> src/main.rs:28:1
|
18 | / impl<T> FooTrait for Foo<T>
19 | | where
20 | | T: MyF64,
21 | | {
... |
24 | | }
25 | | }
| |_- first implementation here
...
28 | / impl<T> FooTrait for Foo<T>
29 | | where
30 | | T: MyU32,
31 | | {
... |
34 | | }
35 | | }
| |_^ conflicting implementation for `Foo<_>`
严格来说,我们可以通过为以下类型添加更好的特征来修复这个实验:
// External libraries
use std::fmt::Display;
// Trait gives the name
trait MyTrait {
fn name(&self) -> String;
}
impl MyTrait for f64 {
fn name(&self) -> String {
"f64".to_string()
}
}
impl MyTrait for u32 {
fn name(&self) -> String {
"u32".to_string()
}
}
// Some kind of struct
struct Foo<T> {
t: T,
}
impl<T> Foo<T>
where
T: MyTrait + Display,
{
fn printme(&self) {
println!("In a {}: {}", self.t.name(), self.t);
}
}
// Takes a foo
fn foo<T>(x: Foo<T>)
where
T: MyTrait + Display,
{
x.printme();
}
fn main() {
// Try both cases
foo(Foo { t: 1.2 });
foo(Foo { t: 12 });
}
给出正确的输出:
In a f64: 1.2
In a u32: 12
也就是说,这段代码比较简单,所以这种修复很容易。更一般地说,我有一个依赖于用户定义数据的结构。这些数据必然有一组不同的关联方法,并且很难强制每种数据都有一个共同的接口。但是,依赖于这些数据的结构只要知道它拥有什么样的数据,就可以很好地吸收这些信息。理论上,我们可以定义两个不同的结构来接受两种不同的数据,并让这些结构实现一个公共接口。也就是说,我真的想要访问一组通用的字段,并且宁愿不必定义许多 setter 和 getter。有没有更好的方法来做到这一点?
【问题讨论】:
-
没有。使用 getter 和 setter 定义特征。或者定义一个具有所有公共字段的单一公共类型,将其嵌入到您的其他类型中,然后为该类型定义一个 getter/setter。
-
请注意,没有什么可以阻止用户传递实现
MyF64和MyU32的T,此时编译器无法知道要使用哪个printme。 -
@Jmb 这更好地解释了这种设计通常无法解决的问题。赞赏。
标签: rust