Why does pattern matching on a union have an unreachable pattern warning?

前端 未结 1 1196
野趣味
野趣味 2021-01-19 04:46

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);
         


        
1条回答
  •  陌清茗
    陌清茗 (楼主)
    2021-01-19 05:16

    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),
        }
    }
    

    0 讨论(0)
提交回复
热议问题