How to work with b".." and HashMap with Vec<u8> keys?

Hello everyone!

I have a HashMap with a byte vector as key. Currently I’m trying to figure out how to use contains_key with a byte string literal. Ideally I would like something like this:

use std::collections::HashMap;

fn main() {
    let mut map: HashMap<Vec<u8>, bool> = HashMap::new();
    
    if map.contains_key(b"xyz") {
        println!("Something");
    }
}

Unfortunately that doesn’t quite work, I get the error message

the trait `std::borrow::Borrow<[u8; 3]>` is not implemented for `std::vec::Vec<u8>`

The solution I came up with is to use map.contains_key(&b"xyz".to_vec());, but I’m hoping there is a less verbose way to do this.

Interestingly the String equivalent works fine (see below), but unfortunately I’m dealing with arbitrary byte sequences and can’t guarantee valid UTF-8.

use std::collections::HashMap;

fn main() {
    let mut map: HashMap<String, bool> = HashMap::new();
    
    if map.contains_key("xyz") {
        println!("Something");
    }
}

Thanks!

b"xyz".as_ref(), b"xyz" as &[_] or &b"xyz"[..] also work, and both of these don’t use an intermediate Vec
This is happening because Rust doesn’t have great support for fixed-sized arrays yet. This will hopefully be solved once we have const-generics.

2 Likes

How would const generics solve this problem? Would fixed-size arrays and slices use the same type constructor?

How would const generics solve this problem? Would fixed-size arrays and slices use the same type constructor?

It is not about this. As you see compiler complains about unimplemented trait Borrow. If you check String documentation you will see that Borrow<str> is implemented for String, but this is not the case for Borrow<[u8; x]>. Now why people do not implement Trait<[u8; x]>? Because at the time being you can only do this if you go through every possible x and implement for them separately: this is how a bunch of traits (e.g. see 32 Debug implementations) is implemented for fixed-size arrays and nobody wants to burden compiler with that many implementations unless necessary. With const generics you can just have one implementation for all possible xes.

I do not think constant generics will solve the problem in this case though: you can’t exactly borrow vector with non-three elements as three element fixed-size array: first, fixed-size arrays do not have their size stored anywhere but in their type, so if size of array is greater then size of vector Borrow<[T; x]> for Vec<T> has only two options: risk out-of-bounds array access when using borrow or panic. Second, for the same reason if size of fixed-size array is less then size of the vector it will suddenly appear in your case that Vec<u8> containing abcdef when borrowed as [u8; 3] is equal to b"abc". In other words, it will violate requirement that

In particular Eq , Ord and Hash must be equivalent for borrowed and owned values: x.borrow() == y.borrow() should give the same result as x == y .

1 Like

Oh, right, Borrow.

We could introduce yet another trait, TryBorrow which could fail and that could be used to replace Borrow in the trait bounds while maintaining backwards compatibility. Using this in combination with const-generics we can express use arrays directly to get stuff from HashMap or similar structures.

(I am not advocating this, I am simply pointing out a way we could incorporate const-generics)

Regarding the OP, using .contains_key::<[u8]> will allow you to use byte-string literals:

use ::std::collections::HashMap;

fn main ()
{
    let mut map: HashMap<Vec<u8>, bool> = HashMap::new();
    
    if map.contains_key::<[u8]>(b"xyz") {
        println!("Something");
    }
}
4 Likes