How to define a family of structs with different const parameters in Rust?

Viewed 808

I ran into this problem while reading the struct definitions for page tables (used in an operating system source code targeting x86_64 platform). The definitions are shown below:

pub trait TableLevel {}

pub enum Level4 {}
pub enum Level3 {}
pub enum Level2 {}
pub enum Level1 {}

impl TableLevel for Level4 {}
impl TableLevel for Level3 {}
impl TableLevel for Level2 {}
impl TableLevel for Level1 {}

pub trait HierarchicalLevel: TableLevel {
    type NextLevel: TableLevel;
}

impl HierarchicalLevel for Level4 {
    type NextLevel = Level3;
}

impl HierarchicalLevel for Level3 {
    type NextLevel = Level2;
}

impl HierarchicalLevel for Level2 {
    type NextLevel = Level1;
}

This piece of Rust code seems not very clever. I'm wondering if I can parameterize the level number, e.g. 1,2,3,4. With C++, I can achieve this easily:

#include <type_traits>
#include <iostream>

template <unsigned L>
struct Level {
    typedef Level<L-1> NextLevel;
};

template <>
struct Level<1> {};

int main() {
    // The output below will give "1".
    // It checks the type `Level<3>::NextLevel::NextLevel`
    // and the type `Level<2>::NextLevel` are indeed the same type.
    std::cout
        << std::is_same<Level<3>::NextLevel::NextLevel,
                        Level<2>::NextLevel>::value
        << std::endl;
    return 0;
}

I tried to do the same thing in Rust, but I couldn't because Rust wouldn't allow me do to arithmetic operations with constant parameters.

#![feature(min_const_generics)]

struct Level<const LEVEL: usize> {}
impl <const LEVEL: usize> TableLevel for Level<LEVEL> {}

impl <const LEVEL: usize> HierarchicalLevel for Level<LEVEL> {
    // Error message from the compiler:
    // generic parameters may not be used in const operations
    // cannot perform const operation using `LEVEL`
    // help: const parameters may only be used as standalone arguments, i.e. `LEVEL`
    type NextLevel = Level<{LEVEL - 1}>;
}

Is it possible to parameterize the level number in the current version (possibly nightly) of Rust?

1 Answers

The unstable language feature, min_const_generics is quite limited in what it lets you do. As you discovered, you can't use expressions involving generic constants in type arguments.

However, in stable Rust, you can use the typenum crate, which works in a similar way to what you were trying, but does all of the type-level boilerplate for you.

use std::ops::Sub;
use typenum::{Unsigned, U1, U2, U3}; // 1.12.0

struct Level<N: Unsigned>(N);

impl<N: Unsigned> Level<N> {
    fn value() -> usize {
        N::to_usize()
    }
}

trait HierarchicalLevel {
    type NextLevel;
}

impl<N> HierarchicalLevel for Level<N>
where
    N: Sub<U1> + Unsigned,
    <N as Sub<U1>>::Output: Unsigned,
{
    type NextLevel = Level<<N as Sub<U1>>::Output>;
}

fn main() {
    assert_eq!(
        <Level::<U3> as HierarchicalLevel>::NextLevel::value(),
        Level::<U2>::value()
    );
}

The types U0, U1, U2,... represent the unsigned integers 0, 1, 2,...

Related