可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
If I have a type like MyEnum<T>
, how can I map it in cases where not every variant is parameterized?
For example, I'd like to convert from MyEnum<u32>
to MyEnum<String>
:
enum MyEnum<T> {
B,
C,
D(T),
}
fn trans(a: MyEnum<u32>) -> MyEnum<String> {
match a {
MyEnum::D(i) => MyEnum::D(i.to_string()),
other_cases => other_cases,
}
}
fn main() {}
This fails with:
error[E0308]: match arms have incompatible types
--> src/main.rs:8:9
|
8 | match a {
| ^ expected struct `std::string::String`, found u32
|
= note: expected type `MyEnum<std::string::String>`
= note: found type `MyEnum<u32>`
note: match arm with an incompatible type
--> src/main.rs:10:28
|
10 | other_cases => other_cases,
| ^^^^^^^^^^^
Instead of the other_cases => other_cases
line, I tried this, also without success:
other_cases => {
let o: MyEnum<String> = other_cases;
o
}
回答1:
macro_rules! partial_enum {
($name: ident, $some: ident, $($none: ident),+) => {
#[derive(Debug)]
enum $name<T> {
$some(T),
$($none),+
}
impl<T> $name<T> {
fn convert<U>(self) -> Result<$name<U>, T> {
match self {
$name::$some(x) => Err(x),
$($name::$none => Ok($name::$none)),+
}
}
}
}
}
partial_enum!(MyEnum, D, B, C);
fn trans(a: MyEnum<u32>) -> MyEnum<String> {
let a_split: Result<MyEnum<String>, u32> = a.convert();
match a_split {
Ok(is_none) => is_none,
Err(not_none) => MyEnum::D(not_none.to_string()),
}
}
fn main() {
println!("{:?}", trans(MyEnum::D(13)));
}
回答2:
I'd create a map
method on your enum:
#[derive(Debug)]
enum MyEnum<T> {
B,
C,
D(T),
}
impl<T> MyEnum<T> {
fn map<F, U>(self, f: F) -> MyEnum<U>
where F: FnOnce(T) -> U
{
use MyEnum::*;
match self {
B => B,
C => C,
D(x) => D(f(x)),
}
}
}
fn main() {
let answer = MyEnum::D(42);
let answer2 = answer.map(|x| x.to_string());
println!("{:?}", answer2);
}
This is similar to existing map
methods, such as Option::map
.
回答3:
Well, this is actually an answer:
enum MyEnum<T> {
B,
C,
D(T),
}
fn trans(a: MyEnum<u32>) -> MyEnum<String> {
match a {
MyEnum::D(i) => MyEnum::D(i.to_string()),
MyEnum::B => MyEnum::B,
MyEnum::C => MyEnum::C
}
}
fn main() {
}
But repeating all variants isn't acceptable when there's a lot of them..
回答4:
Some languages (like C++), use Duck Typing: if it quacks like a duck, it must be a duck, and therefore names matter. Rust does not.
In Rust, names are just some display utility for us mere humans, the B
in MyEnum<u32>
and MyEnum<String>
may happen to have the same visual representation, but they are completely different syntactic entities as far as the language is concerned.
There are multiple ways to alleviate your pain, though:
- a code generation plugin or
build.rs
script can be used as well
- a macro can be used to automate the mapping
- a manual mapping can be done, it's a one shot effort after all
- the code can be restructured to separate type-dependent from type-independent variants
I'll show-case the latter:
enum MyEnumImpl {
A,
B,
C,
}
enum MyEnum<T> {
Independent(MyEnumImpl),
Dependent(T),
}
Obviously, the latter makes it much easier to manually map things.