0

I'm trying to create a Uuid in a const context by passing in a &str slice but I keep getting various errors.

This answer gave me the non-const solution but it doesn't seem to work in const due to the reference.

How can I use the first 16 bytes of the hash as an input for a Uuid? Is this actually possible in a const context?

struct ExampleId(Uuid);

pub const fn from_str(value: &str) -> ExampleId {
    // Hash the str slice to get a "random" value
    let hash: const_sha1::Digest =
        const_sha1::sha1(&const_sha1::ConstBuffer::from_slice(value.as_bytes()));
    // This gives a 20 byte hash
    let bytes: [u8; 20] = hash.bytes();
    // We only need 16 of these for a UUID but it doesn't seem possible to do this in a const context
    let bytes_16: [u8; 16] = bytes[..16].try_into().unwrap();

    ExampleId(Uuid::from_bytes(bytes_16))
}

const EXAMPLE: ExampleId = ExampleId::from_str("example_id");
error[E0277]: the trait bound `[u8; 16]: From<&[u8]>` is not satisfied
  --> crates/bevy_diagnostic/src/diagnostic.rs:22:46
   |
22 |         let bytes_16: [u8; 16] = bytes[..16].try_into().unwrap();
   |                                              ^^^^^^^^ the trait `~const From<&[u8]>` is not implemented for `[u8; 16]`
   |
   = note: required for `&[u8]` to implement `~const Into<[u8; 16]>`
   = note: required for `[u8; 16]` to implement `~const TryFrom<&[u8]>`
   = note: required for `&[u8]` to implement `~const TryInto<[u8; 16]>`

For more information about this error, try `rustc --explain E0277`.
error: could not compile `bevy_diagnostic` due to previous error

For added context, currently this code uses a u128 and looks something like ExampleId::from_u128(0x8EE938B3184729691BCCD346823B631C), with the user having to generate a unique 128 bit number/hex. To make it easier, I would like to allow passing in a string to generate the Id. The UUID is an exposed part of the API so it can't be changed which means I need to generate a UUID from an arbitrary string slice. Finally, because this ID is required at compile time, this needs to be done as a const function.

Michael
  • 507
  • 4
  • 11

2 Answers2

1

I mean there is this dumb solution copying the bytes one by one maybe somewhere there is a macro for it:

let bytes_16: [u8; 16] = [
    bytes[0], bytes[1], bytes[2], bytes[3],
    bytes[4], bytes[5], bytes[6], bytes[7],
    bytes[8], bytes[9], bytes[10], bytes[11],
    bytes[12], bytes[13], bytes[14], bytes[15],
];

If you're willing to use unafe you can also let the compiler generate all the copying code for you:

// SAFETY: we assert that at least 16 elements are known good, so we can safely convert to a *const [_; 16] and dereference that.
let bytes_16 = unsafe {
    assert!(bytes.len() >= 16);
    *(bytes.as_ptr() as *const [_; 16])
};

Both of these do not initialize the final array twice unlike the Sven Marnachs solution, but they both have other drawbacks instead, admittedly in this case that's not really a concern unless you use it many times.

cafce25
  • 15,907
  • 4
  • 25
  • 31
1

For loops are not allowed in const functions yet, but you can use a while loop:

let mut bytes_16 = [0; 16];
let mut i = 0;
while i < 16 {
    bytes_16[i] = bytes[i];
    i += 1;
}
Sven Marnach
  • 574,206
  • 118
  • 941
  • 841