Можно ли упростить это выражение соответствия Rust? - PullRequest
0 голосов
/ 31 октября 2018

У меня есть структура с 6 полями одного типа. Я хочу быть в состоянии получить изменяемые ссылки на них из перечисления.

Например, с учетом следующих упрощенных определений:

struct S {
    a: i32,
    b: i32,
    cs: [i32; 3],
    d: i32,
}

enum E {
    A,
    B,
    COrD(COrD)
}

enum COrD {
    C1,
    C2,
    C3,
    D,
}

Я хочу написать такую ​​функцию:

fn get_refs_mut<'a, T>(s: &'a mut S, e1: E, e2: E) -> RefsMut<'a, i32> {
    match (e1, e2) {
        (E::A, E::A) => RefsMut::Same(&mut S.a),
        (E::A, E::B) => RefsMut::Pair(&mut S.a, &mut S.b),
        (E::B, E::COrD(COrD::C1)) => RefsMut::Pair(&mut S.b, &mut S.c[0])
        // ...
        (E::B, E::A) => RefsMut::Pair(&mut S.b, &mut S.a),
        (E::B, E::B) => RefsMut::Same(&mut S.b),
        (E::B, E::COrD(COrD::C1)) => RefsMut::Pair(&mut S.b, &mut S.c[0]),
        // ...
        (E::COrD(COrD::D), E::COrD(COrD::D)) => RefsMut::Same(&mut S.d),
    }
}

enum RefsMut<'a, T> {
    Pair(&'a mut T, &'a mut T),
    Same(&'a mut T),
}

Но, насколько я могу судить, для этого потребуется вручную указать результаты для всех 6 * 6 = 36 возможных значений для (e1, e2), что является трудоемким. Есть ли способ написать эту функцию более кратко?

В сторону: конкретный способ, которым я определил S и E, может показаться странным. У меня есть причины иметь эквивалент COrD в реальном коде, поэтому я могу ссылаться на эти конкретные поля и связанные поля отдельно. Точно так же реальные версии cs также упоминаются вместе, следовательно, они находятся в массиве.

1 Ответ

0 голосов
/ 31 октября 2018

Сначала напишите функцию, которая получает ссылку на один член. Затем дважды вызовите эту функцию:

fn get_ref_mut<'a>(s: &'a mut S, e: E) -> &'a mut i32 {
    match e {
        E::A => &mut s.a,
        E::B => &mut s.b,
        E::COrD(c) => match c {
            COrD::C1 => &mut s.cs[0],
            COrD::C2 => &mut s.cs[1],
            COrD::C3 => &mut s.cs[2],
            COrD::D => &mut s.d,
        }
    }
}

fn get_refs_mut<'a>(s: &'a mut S, e1: E, e2: E) -> RefsMut<'a, i32> {
    if e1 == e2 {
        RefsMut::Same(get_ref_mut(s, e1))
    } else {
        let first: &mut i32 = unsafe {
            &mut *(get_ref_mut(s, e1) as *mut i32)
        };

        let second = get_ref_mut(s, e2);
        RefsMut::Pair(first, second)
    }
}

Блок unsafe необходим, потому что нам нужно преобразовать одну из ссылок на указатель, а затем разыменовать ее, чтобы обмануть средство проверки заимствований в мысли, что first не ссылается на s. Таким образом, это позволит нам одолжить его во второй раз в second. Но наш код по-прежнему безопасен, потому что мы структурировали его так, чтобы мы гарантированно ссылались на 2 разные части s. Это похоже на то, что делает split_at_mut, как объяснено в The Rustonomicon .

Обратите внимание, что выражение e1 == e2 потребует от вас реализации (или получения) PartialEq для E и COrD. Если по какой-либо причине вы не можете этого сделать, вы можете сравнить first и second как указатели, чтобы увидеть, ссылаются ли они на одно и то же.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...