Как мне соблюдать границы времени жизни при передаче функций в качестве аргумента? - PullRequest
0 голосов
/ 06 мая 2020

Исходный выпуск

Этот фрагмент кода относительно похож на фрагмент кода, который я пытаюсь исправить. Я также спросил об этом на форуме пользователя Rust .

игровая площадка

/// assume this function can't be modified.
fn foo<A>(
    f1: impl Fn(&str) -> Result<(&str, A), ()>,
    base: &str,
    f2: impl Fn(A) -> bool
) {
    let s: String = base.to_owned();
    let option = Some(s.as_ref());
    let mapped = option.map(f1);
    let r = mapped.unwrap();
    let (rem, prod) = r.unwrap();
    assert!(f2(prod));
    assert_eq!(rem.len(), 0);
}


fn main() {
    fn bar<'a>(s: &'a str) -> Result<(&'a str, &'a str), ()> {
        Ok((&s[..1], &s[..]))
    }


    fn baz(s: &str) -> Result<(&str, &str), ()> {
        Ok((&s[..1], &s[..]))
    }

    foo(bar, "string", |s| s.len() == 5); // fails to compile

    foo(baz, "string", |s| s.len() == 5); // fails to compile 
}
error[E0271]: type mismatch resolving `for<'r> <for<'a> fn(&'a str) -> std::result::Result<(&'a str, &'a str), ()> {main::bar} as std::ops::FnOnce<(&'r str,)>>::Output == std::result::Result<(&'r str, _), ()>`
  --> src/main.rs:27:5
   |
2  | fn foo<A>(
   |    ---
3  |     f1: impl Fn(&str) -> Result<(&str, A), ()>,
   |                          --------------------- required by this bound in `foo`
...
27 |     foo(bar, "string", |s| s.len() == 5); // fails to compile
   |     ^^^ expected bound lifetime parameter, found concrete lifetime

Изменить:

Основываясь на рекомендациях ряда людей здесь, в ветке внутренних компонентов , которую я сделал , и на форуме пользователей rust я изменил свой код, чтобы упростить его, используя черту оболочки.

игровая площадка


trait Parser<'s> {
    type Output;

    fn call(&self, input: &'s str) -> (&'s str, Self::Output);
}

impl<'s, F, T> Parser<'s> for F
where F: Fn(&'s str) -> (&'s str, T) {
    type Output = T;
    fn call(&self, input: &'s str) -> (&'s str, T) {
        self(input)
    }
}

fn foo<F1, F2>(
    f1: F1,
    base: &'static str,
    f2: F2
) 
where 
    F1: for<'a> Parser<'a>,
    F2: FnOnce(&<F1 as Parser>::Output) -> bool
{
    // These two lines cannot be changed.
    let s: String = base.to_owned();
    let str_ref = s.as_ref();

    let (remaining, produced) = f1.call(str_ref);
    assert!(f2(&produced));
    assert_eq!(remaining.len(), 0);
}

struct Wrapper<'a>(&'a str);

fn main() {
    fn bar<'a>(s: &'a str) -> (&'a str, &'a str) {
        (&s[..1], &s[..])
    }

    fn baz<'a>(s: &'a str) -> (&'a str, Wrapper<'a>) {
        (&s[..1], Wrapper(&s[..]))
    }

    foo(bar, "string", |s| s.len() == 5); // fails to compile

    foo(baz, "string", |s| s.0.len() == 5); // fails to compile 
}

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

error: internal compiler error: src/librustc_infer/traits/codegen/mod.rs:61: Encountered error `OutputTypeParameterMismatch(Binder(<[closure@src/main.rs:45:24: 45:40] as std::ops::FnOnce<(&<for<'a> fn(&'a str) -> (&'a str, &'a str) {main::bar} as Parser<'_>>::Output,)>>), Binder(<[closure@src/main.rs:45:24: 45:40] as std::ops::FnOnce<(&&str,)>>), Sorts(ExpectedFound { expected: &str, found: <for<'a> fn(&'a str) -> (&'a str, &'a str) {main::bar} as Parser<'_>>::Output }))` selecting `Binder(<[closure@src/main.rs:45:24: 45:40] as std::ops::FnOnce<(&&str,)>>)` during codegen

thread 'rustc' panicked at 'Box<Any>', src/librustc_errors/lib.rs:875:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

note: the compiler unexpectedly panicked. this is a bug.

note: we would appreciate a bug report: https://github.com/rust-lang/rust/blob/master/CONTRIBUTING.md#bug-reports

note: rustc 1.43.0 (4fb7144ed 2020-04-20) running on x86_64-unknown-linux-gnu

note: compiler flags: -C codegen-units=1 -C debuginfo=2 --crate-type bin

note: some of the compiler flags provided by cargo are hidden

error: aborting due to previous error

error: could not compile `playground`.

To learn more, run the command again with --verbose.

Я сделал отчет об ошибке здесь .

Ответы [ 2 ]

2 голосов
/ 06 мая 2020

Посмотрите на первый аргумент функции:

f1: impl Fn(&str) -> Result<(&str, A), ()>,

Откуда могло взяться значение типа A? Это должно быть либо:

  • , полученное из str в аргументе, либо
  • извлеченное из ниоткуда, что будет означать, что это 'static

Но A объявлен для foo, а не для указанного c f1 аргумента. Это означает, что время жизни A не может зависеть от аргумента f1. Но это именно то, что делают bar и baz.

Итак, что вы можете сделать? Учитывая ваше требование «предположить, что эта функция не может быть изменена», вы застряли с изменением bar и baz, чтобы тип A был stati c. Это дает вам выбор из недавно выделенных String или &'static str:

fn bar<'a>(s: &'a str) -> Result<(&'a str, String), ()> {
    Ok((&s[..1], s[..].to_owned()))
}

Или:

fn bar<'a>(s: &'a str) -> Result<(&'a str, &'static str), ()> {
    Ok((&s[..1], "hello"))
}

Если бы вы были возможность изменить сигнатуру типа foo, вы можете использовать ссылки на A в сигнатурах функций аргументов, что позволит вам описать их время жизни по отношению к другим их аргументам:

Например:

fn foo<A: ?Sized>(
    f1: impl Fn(&str) -> Result<(&str, &A), ()>,
    base: &str,
    f2: impl Fn(&A) -> bool
) {
    unimplemented!()
}

Что эквивалентно следующему, без исключения времени жизни:

fn foo<A: ?Sized>(
    f1: impl for<'a> Fn(&'a str) -> Result<(&'a str, &'a A), ()>,
    base: &str,
    f2: impl for<'a> Fn(&'a A) -> bool
) {
    unimplemented!()
}

Обратите внимание, что сигнатура типа f1 теперь выражает связь между временем жизни входа &str и &A в результате.

0 голосов
/ 07 мая 2020

Для всех, кто прочитает это позже и задается вопросом, нашел ли я когда-нибудь обходной путь, я нашел!

Этот обходной путь включает помещение состояния, на которое ссылаются вызовы функции, в структуру Context. Из-за текущих проблем с границами признаков высокого ранга (HRTB) этот способ обхода их полностью избегает. Вместо этого мы реорганизуем инициализацию объекта (что может быть дорогостоящим) в конструкторе Context. Контекст полностью владеет состоянием функции. Это нормально, поскольку функции нужна только ссылка на это состояние, а не владение. Каждый раз, когда нам нужно вызвать функцию, мы передаем ее в функцию вызова контекста, которая гарантирует, что время жизни аргументов функции и ее вывода соответствует времени жизни контекста в /, в котором она выполняется.

детская площадка

// Workaround code: the state of the function is placed 
// into a struct so that all of the references are valid
// for the lifetime of of &self. This way concrete lifetimes
// can be used because all lifetimes start on the function 
// call, rather than several statements into the function. 
//
// This work around eliminates the need for Higher Ranked 
// Trait Bounds (HRTBs), since all lifetimes are instantiated
// on function initialization.

struct Wrapper<'a>(&'a str);

struct ParserContext {
    inner: String
}

impl ParserContext {
    fn new(base: &str) -> Self {Self {inner: base.to_owned()}}

    fn call<'a, O>(
        &'a self, 
        f1: fn(&'a str) -> (&'a str, O),
        f2: fn(O) -> bool,
    ) {
        let (remaining, produced) = f1(self.inner.as_str());
        assert_eq!(remaining.len(), 0);
        assert!(f2(produced));
    }
}


fn main() {
    fn bar(s: &str) -> (&str, &str) {
        (&s[..0], &s[..])
    }

    fn baz(s: &str) -> (&str, Wrapper) {
        (&s[..0], Wrapper(&s[..]))
    }


    // I tried to extract the section below into its
    // own function previously (foo) but this would
    // always inevitably fail because of lifetime issues.
    // this work around only adds one line of code to the calling
    // function to create the context to hold the state 
    // used by the functions passed to `call`, so this 
    // is an acceptable, and rather eloquent work around.
    let pc = ParserContext::new("string");

    pc.call(bar, |s| s.len() == 6);
    pc.call(baz, |s| s.0.len() == 6);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...