Why does pattern matching on a union have an unrea

2020-04-12 03:25发布

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

1条回答
Viruses.
2楼-- · 2020-04-12 03:51

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),
    }
}
查看更多
登录 后发表回答