Более простой способ снабдить generi c структуры константой в Rust - PullRequest
0 голосов
/ 06 февраля 2020

У меня есть тип 'Indexer', который идет вперед (+1) или назад (-1). В C ++:

enum Direction {
    Forward,
    Backward
};

template <Direction Dir>
struct Indexer {
   static constexpr int delta = (Dir == Forward ? 1 : -1);
};

void foo() {
    Indexer<Forward> f;
}

Я хотел бы реализовать это в Rust. Это лучшее, что я придумал:

use std::marker::PhantomData;

#[derive(Copy, Clone)]
pub struct Forward;

#[derive(Copy, Clone)]
pub struct Backward;

pub trait Direction : Copy {
    const DELTA: i32;
}

impl Direction for Forward {
    const DELTA: i32 = 1;
}

impl Direction for Backward {
    const DELTA: i32 = -1;
}

#[derive(Copy, Clone)]
pub struct Indexer<Dir: Direction> {
    pd: PhantomData<Dir>
}

impl<Dir: Direction> Indexer<Dir> {
    const DELTA:i32 = Dir::DELTA;
}

pub fn run() {
    let idx = Indexer::<Forward>{pd: PhantomData{}};
}

Кажется, что PhantomData требуется, потому что я не храню Direction в поле, а это, в свою очередь, требует большого количества шаблонов. Есть ли более простой способ портировать этот код C ++? Я надеюсь избежать макросов или ящиков.

Ответы [ 2 ]

1 голос
/ 07 февраля 2020

Я придумал что-то, что я использую в своих проектах (пока ржавчина не поддерживает обобщенные константы):

use std::fmt;

pub trait TStaticValue<
    V: Copy/*prevent interior mutation (suggested by clippy)*/
> : Sync + 'static + Clone + fmt::Debug { // these bounds turned out to be convenient for me
    const VALUE : V;
}

macro_rules! define_static_value {(pub $struct: ident, $type: ty, $value: expr) => {
    #[derive(Clone, Debug)]
    pub struct $struct {}
    impl TStaticValue<$type> for $struct {
        const VALUE : $type = $value;
    }
}}

Тогда define_static_value позволит вам определить следующее:

define_static_value!(pub ForwardDelta, i32, 1);
define_static_value!(pub BackwardDelta, i32, -1);

pub struct Indexer<Dir: TStaticValue<i32>> {
    pd: std::marker::PhantomData<Dir>
}

impl<Dir: TStaticValue<i32>> Indexer<Dir> {
    const DELTA:i32 = Dir::VALUE;
}

Я знаю, что вы хотели избежать макросов, но это решение оказалось для меня относительно легким в обслуживании, потому что все "значения stati c" (т.е. параметры const generi c) эмулируются одинаково, и это позволяет Вы можете определить и использовать их как ho c и с разными типами, все через одну и ту же архитектуру: всякий раз, когда вам действительно нужен параметр const generi c Param типа CG, вместо этого вы указываете Param: TStaticValue<CG>.

Возможно, вы захотите настроить define_static_value, чтобы учесть значения, отличные от pub.

Замечание относительно PhandomData: я до сих пор не нашел способа, который позволил бы мне избегайте их, но вы можете убедиться, что каждая структура имеет не более одного PhantomData члена, упаковав все остальные неиспользуемые параметры в это соответствующее поле (которое тогда будет иметь тип PhantomData<(FirstUnused, SecondUnused, ThirdUnused)>.

1 голос
/ 06 февраля 2020

Это на самом деле зависит от того, что именно вы пытаетесь сделать здесь, но есть одно упрощение:

#[derive(Copy, Clone)]
enum Direction {
    Forward = 1,
    Backward = -1,
}

impl Direction {
    const fn delta(self) -> isize {
        self as isize
    }
}

Когда if и match разрешено в const функциях , это, возможно, более идиотски, можно записать как

#[derive(Copy, Clone)]
enum Direction {
    Forward,
    Backward,
}

impl Direction {
    const fn delta(self) -> isize {
        match self {
            Self::Forward => 1,
            Self::Backward => -1,
        }
    }
}

Или вы просто не можете принять delta как константу. В большинстве случаев компилятор все равно оптимизирует вызовы к нему. Только если вам нужно, чтобы результат был константным выражением, это необходимо.

Вот как вы можете использовать это (детская площадка) :

fn main() {
    let dir = Direction::Forward;

    let mut x: isize = 0;
    for _ in 0..100 {
        x += dir.delta();
    }

    let dir = Direction::Backward;

    for _ in 0..50 {
        x += dir.delta();
    }

    assert_eq!(x, 50);
    println!("{}", x);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...