8

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
  • 4
    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 Commented Aug 31, 2014 at 7:38
  • Than I guess I have to wait :( But thanks for the link to the GitHub issue Commented Aug 31, 2014 at 15:44

2 Answers 2

5

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:

Sign up to request clarification or add additional context in comments.

Comments

0

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

4 Comments

You have misunderstood what the OP wants. They are looking for const generics.
Ooh okay, that's my bad. Worth deleting the answer, or should I leave it?
You could change it to show how to implement it today using macros and associated constants. Maybe followed by a hypothetical example of what it would look like once the RFC is implemented.
Okay, I'll come up with something sometime soon.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.