5
votes

I'm trying to migrate some C++ code to Rust. I have tried lots of different approaches but none of them compile.

I want a generic template that can handle different types and has a adjustable total size with a static field (const expression) Capacity:

template<class KeyType, class ValueType, int PageSize>
struct BplusTreeLeaf {
    static const uint16_t Capacity = (PageSize-16)/(sizeof(KeyType)+sizeof(ValueType));
    KeyType keys[Capacity];
    ValueType values[Capacity];
};

I want to access the capacity from outside:

for(int i = 0; i < BplusTreeLeaf<x, y, 4096>::Capacity; i ++) { ... }

It seems that there is no way to do something like this in Rust, or at least in my understanding of Rust:

  • static is not allowed in a struct and the documentation tells me to use macros
  • only types can be "templated" in Rust but not values or expressions. I can't even pass the total size as an argument to the struct definition

This is as far as I got:

macro_rules! BplusTreeLeaf {
    ($KeyType:ident, $ValueType:ident, $PageSize:expr) => {
        static Capacity_: u16 = ($PageSize - 16) / (std::mem::size_of::<$KeyType>() + std::mem::size_of::<$ValueType>());
        struct BplusTreeLeaf_ {
            keys: [$KeyType, ..Capacity_],
            values: [$ValueType, ..Capacity_],
        }
    }
}

BplusTreeLeaf!(u64, u64, 4096)

The compiler yields "expected constant expr for vector length" which is incorrect because I did not use "mut" for Capacity_, so it has to be a const expression. Even if it would work, Capacity_ and BplusTreeLeaf_ would still be in the global scope/namespace.

Have I misunderstood something elementary in Rust's design or is it just impossible? If it isn't possible now, is there something planned as a future feature or should I stay with C++ 11?

2
Yep, a lot of unimplemented features. Compiler gives "expected constant expr" error because mem::size_of is an ordinary function and it is not "constexpr". AFAIK, they are going to add "constexpr" and "non-type template parameters" somewhere in the future, after 1.0, and "static members" sooner, before 1.0, with this RFC: github.com/rust-lang/rfcs/pull/195 - user2665887
Than I guess I have to wait :( But thanks for the link to the GitHub issue - Alexander Meißner

2 Answers

1
votes

The primary feature you are looking for is called const generics. Basic support is available in Rust 1.51:

struct BplusTreeLeaf<K, V, const CAP: usize> {
    keys: [K; CAP],
    values: [V; CAP],
}

impl<K, V, const CAP: usize> BplusTreeLeaf<K, V, CAP> {
    const CAPACITY: usize = CAP;
}

fn main() {
    println!("{}", BplusTreeLeaf::<u8, f32, 16>::CAPACITY);
    println!("{}", BplusTreeLeaf::<i32, bool, 32>::CAPACITY);
}

As you mentioned, you can create a macro for previous versions of Rust which will create one-off types with a specific capacity:

macro_rules! make_leaf {
    ($name:ident, $capacity:expr) => {
        struct $name<K, V> {
            keys: [K; $capacity],
            values: [V; $capacity],
        }
        
        impl<K, V> $name<K, V> {
            const CAPACITY: usize = $capacity;
        }
    }
}

make_leaf!(BplusTreeLeaf16, 16);
make_leaf!(BplusTreeLeaf32, 32);

fn main() {
    println!("{}", BplusTreeLeaf16::<u8, f32>::CAPACITY);
    println!("{}", BplusTreeLeaf32::<i32, bool>::CAPACITY);
}

See also:

0
votes

This question is old, but still valid. You can use the lazy_static crate.

Normal static variables must be initialized with constant expressions, which may include constant functions. This allows them to be evaluated at compile time, and baked into the binary. However, this imposes many limitations, such as no heap allocations.

You want to evaluate the constant when the program is first run. Like this example, straight from the crate's page:

lazy_static! {
    static ref HASHMAP: HashMap<u32, &'static str> = {
        let mut m = HashMap::new();
        m.insert(0, "foo");
        m.insert(1, "bar");
        m.insert(2, "baz");
        m
    };
}

fn main() {
    // First access to `HASHMAP` initializes it
    println!("The entry for `0` is \"{}\".", HASHMAP.get(&0).unwrap());

    // Any further access to `HASHMAP` just returns the computed value
    println!("The entry for `1` is \"{}\".", HASHMAP.get(&1).unwrap());
}