Сигнатуры функций Rust и их жизнь

Чтобы изучить Rust, я пишу библиотеку комбинаторов парсеров. Теперь у меня проблема, связанная со сложным типом среза и владением. У меня есть несколько функций строительных блоков, которые я хотел бы использовать отдельно:

pub fn achar(character: char) -> impl Fn(&str) -> Option<char> {
    move |input| match input.chars().next() {
        Some(c) if c == character => Some(c),
        _ => None,
    }
}

pub fn alternatives<'a, T>(
    alts: &'a [impl Fn(&'a str) -> Option<T>],
) -> impl Fn(&'a str) -> Option<T> {
    move |input| {
        for alt in alts.iter() {
            let tried = alt(input);
            if tried.is_none() {
                continue;
            }
            return tried;
        }

        None
    }
}

pub fn one_of<'a>(allowed: &'a str) -> impl Fn(&'a str) -> Option<char> {
    let v = allowed.chars().map(achar).collect::<Vec<_>>();
    alternatives(&v)
}

Тогда использование будет выглядеть так:

fn main() {
    println!("{:?}", achar('f')("foo"));
    println!("{:?}", alternatives(vec![achar('f'), achar('b')])("foo"));
    println!("{:?}", one_of("foo")("foo"));
}

Вот так я получаю следующую ошибку:

   = note:   expected type `for<'r> fn(&'r str) -> std::option::Option<char> {any_char}`
           found reference `&impl for<'r> std::ops::Fn<(&'r str,)>`

Я бы сказал, что quote реализует Fn(&str), не так ли? Как я могу заставить это работать?


Как только это будет решено, появится следующая проблема:

33 |     alternatives(&v)
   |     ^^^^^^^^^^^^^--^
   |     |            |
   |     |            `v` is borrowed here
   |     returns a value referencing data owned by the current function

Конечно, встраивание не решает проблемы. Я понятия не имею, как можно атаковать это.

Здесь доступна игровая площадка.


person primfaktor    schedule 18.04.2020    source источник
comment
Код в вашем сообщении не приводит к той же ошибке, что и код на игровой площадке.   -  person trentcl    schedule 18.04.2020
comment
Спасибо, trentcl. Пока я писал этот пост, я все еще пытался заставить его работать. Наверное, я перепутал версии.   -  person primfaktor    schedule 18.04.2020


Ответы (1)


Здесь происходит несколько вещей:

pub fn alternatives<'a, T>(
    alts: &'a [impl Fn(&'a str) -> Option<T>],
) -> impl Fn(&'a str) -> Option<T> {
    move |input| {
        ...
    }
}

Вышеупомянутое говорит, что alternatives требует, чтобы все элементы в срезе alts были одного типа, и что этот тип реализует Fn(&'a str) -> Option<T>. Однако в вызове let escaping = alternatives(&vec![any_char, &quote, &backslash]); (или даже просто vec![any_char, &quote, &backslash];) элементы имеют разные типы (один - типа any_char, следующий - типа quote и т. Д.). С другой стороны, vec![achar('f'), achar('b')] работает, поскольку все элементы имеют тип achar.

То есть, даже если все элементы реализуют Fn(&str)->Option, они не совсем одного типа.

По поводу следующей ошибки:

pub fn one_of<'a>(allowed: &'a str) -> impl Fn(&'a str) -> Option<char> {
    let v = allowed.chars().map(achar).collect::<Vec<_>>();
    alternatives(&v)
}

Здесь вы возвращаете alternatives(&v), который занимает v. Но v выходит за рамки в конце one_of, что означает, что результат alternatives будет относиться к чему-то, что было уничтожено, поэтому ржавчина запрещает это.

Теперь, что мы можем сделать с типами, принятыми alternatives?

  • alternatives может принимать функции в штучной упаковке. Это, однако, влечет за собой некоторые накладные расходы во время выполнения, но позволит динамическому количеству альтернатив во время выполнения.
  • alternatives может принимать указатели на функции. Это, однако, запретит закрытие (захват среды).
  • alternatives может принимать 2 функции, и более двух функций может быть предоставлено через макрос. Это в основном исправит количество альтернатив во время компиляции, но не приведет к накладным расходам во время выполнения.

Что мы можем сделать с проблемой времени жизни в one_of?

  • Фактически это также может считаться проблемой в alternatives, поскольку alternatives, возможно, должен принимать некоторые функции в качестве аргументов и сохранять их, чтобы вызывающему абоненту не нужно было беспокоиться о проблемах времени жизни.
person phimuemue    schedule 18.04.2020
comment
Я могу жить с alternatives, взяв на себя ответственность и изменив это. Но я удивлен, что вы не можете передать массив функций, которые все ведут себя одинаково (т.е. одна и та же подпись и все). Будет ли срез работать лучше, чем Vec? - person primfaktor; 18.04.2020
comment
Проблема с функциями, имеющими только одну и ту же сигнатуру, заключается в том, что они могут быть замыканиями, т.е. некоторые могут захватывать переменные (например, вы achar захватываете символ). - person phimuemue; 19.04.2020
comment
Если вы хотите стать владельцем, вероятно, лучшим вариантом будет Vec (поскольку slice не передает свои данные, а Vec это делает). - person phimuemue; 19.04.2020