Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the meaning of casting a Rust enum variant to a numeric data type?

I've found that I can cast an Enum variant to a numeric type, however, it's not 100% clear what's the meaning/usefulness, in particular, because the semantics vary based on certain conditions.

The basic form:

enum Simple {
    A,
    B,
}

println!("{}", Simple::A as u8);   // 0
println!("{:?}", Simple::B as u8); // 1

has a numeric value of an unsigned positive number starting from 0 (at least, for this specific case)

However, variants with embeds are confusing:

enum WithEmbed {
    A(String),
    B,
}

println!("{}", WithEmbed::A as u8);            // 48!?
println!("{}", WithEmbed::A("".into()) as u8); // error!?
println!("{}", WithEmbed::B as u8);            // error!?

What's the meaning of numeric casts of enum variants with embeds?

like image 620
Marcus Avatar asked Oct 15 '25 07:10

Marcus


1 Answers

Casting enums that are not C-like (so, enumerations with fields, like in your second example) to an integer is not possible. This is why the casts in the second example fail.

However, why does this one cast work?

println!("{}", WithEmbed::A as u8);            // 48!?

This is not casting an enum, but casting the enum constructor function to an integer containing the address of the function. You can cast any function to an integer containing its address:

fn main() {
    enum WithEmbed {
        A(String),
        B,
    }
    
    println!("0x{:x}", WithEmbed::A as usize);
    println!("0x{:x}", main as usize);
}

Playground

When running this on the Rust playground, I get:

WithEmbed:A: 0x563813475b70
main:        0x563813475a50

You will probably get something slightly different if you run it on your own machine, but as you can see, this is just casting the function pointer. There is also an issue on Github describing this behavior.


Also, for completeness sake:

The basic form [...] has a numeric value of an unsigned positive number starting from 0 (at least, for this specific case)

Indeed, only in this specific case. The Rust compiler is free to assign other integers or do whatever it wants (even not assign any integer at all – for example, an Option<&T> only stores a raw pointer, if this pointer is NULL, the option is equal to None. This is known as Rust's null pointer optimization, see here near the end).

If you depend on the enum having incrementing values starting from 0, you can either explicitely assign them...

enum Simple {
    A = 0,
    B = 1,
}

...or set the representation to C or an integer type:

// With regards to the value describing enum variant, all three are equivalent
#[repr(C)]
enum SimpleC {
    A,
    B,
}

#[repr(u8)]
enum SimpleU8 {
    A,
    B,
}

#[repr(i8)]
enum SimpleI8 {
    A,
    B,
}

Playground

like image 142
Elias Holzmann Avatar answered Oct 18 '25 02:10

Elias Holzmann



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!