Given the documentation, I cannot understand why the pattern matching on a union doesn't work properly:
union A {
a1: i32,
a2: f32,
}
struct B(A);
let b = B(A { a2: 1.0 });
unsafe {
match b.0 {
A { a1 } => println!("int"),
A { a2 } => println!("float"),
}
}
Outputs "int" with an unreachable warning.
warning: unreachable pattern
--> src/main.rs:12:13
|
12 | A { a2 } => println!("float"),
| ^^^^^^^^
|
= note: #[warn(unreachable_patterns)] on by default
The entire point of a union
is that the compiler doesn't store any information in the union
about what type it is; it's completely up to the programmer. Because of this, there's no information for a match
to use to decide what type the value is.
Because of this, your code is conceptually equivalent to
struct A {
a1: i32,
}
let b = A { a1: 42 };
match b {
A { a1 } => println!("int {}", a1),
A { a1 } => println!("float {}", a1),
}
There's no case in which the second match arm will ever be executed.
In fact, switching back and forth between the fields is a prime usage of a union
:
union A {
i: i32,
f: f32,
}
let a = A { i: 42 };
let b = unsafe { a.f };
println!("{}", b);
You may wish to use an enum
if you want the compiler to keep track of what variant you have. In some contexts, enums are called tagged unions because that's exactly what they are: a union with a tag alongside to identify what the union contains.
Otherwise, you need to track what type is actually in the union in some other manner. One such way is to implement your own tag:
union A {
a1: i32,
a2: f32,
}
struct B {
is_int: bool,
data: A,
}
let b = B {
is_int: false,
data: A { a2: 1.0 },
};
unsafe {
match b {
B {
is_int: true,
data: A { a1 },
} => println!("int {}", a1),
B {
is_int: false,
data: A { a2 },
} => println!("float {}", a2),
}
}
The tag can be anything you can match on:
union A {
a1: i32,
a2: f32,
}
struct B {
kind: Kind,
data: A,
}
enum Kind {
Int,
Float,
}
let b = B {
kind: Kind::Float,
data: A { a2: 1.0 },
};
unsafe {
match b {
B {
kind: Kind::Int,
data: A { a1 },
} => println!("int {}", a1),
B {
kind: Kind::Float,
data: A { a2 },
} => println!("float {}", a2),
}
}
I suppose you could even use an enum around the union...
union A {
a1: i32,
a2: f32,
}
enum B {
Int(A),
Float(A),
}
let b = B::Float(A { a2: 1.0 });
unsafe {
match b {
B::Int(A { a1 }) => println!("int {}", a1),
B::Float(A { a2 }) => println!("float {}", a2),
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With