Общая проблема с истечением срока службы Rust

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

Для краткости следующие типы Callback и Event не представляют полную структуру событий, но проблему все равно следует выделить.

type Callback<Args> = Arc<dyn Fn(&Args)>;

struct Event<Args> {
    handlers: Vec<Callback<Args>>,
}

struct OnChangedArgs<'a, T> {
    items: std::slice::Iter<'a, T>,
}

struct ObservableList<T> {
    items: Vec<T>,
    on_changed: Event<OnChangedArgs<'_, T>>,
                                // ^^^^^ can't elid lifetime
}

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

struct OnChangedArgs<'a, T> {
    items: std::slice::Iter<'a, T>,
}

pub type OnChangedCallback<T> = Arc<dyn Fn(&OnChangedArgs<T>)>;
                                                     // ^^^^^ lifetime elided

struct OnChangedEvent<T> {
    handlers: Vec<OnChangedCallback<T>>,
}

struct ObservableList<T> {
    items: Vec<T>,
    on_changed: OnChangedEvent<T>,
                               
}

Я тупой или это раздражает систему типов Rust???

Просто кажется, что система типов должна уметь видеть насквозь Event, чтобы видеть, что она может скрывать время жизни. Таким образом, вам не придется излишне увеличивать время жизни, чтобы вам приходилось иметь дело с ним выше в другой структуре.


81
2

Ответы:

Вы не можете исключить время жизни в этом контексте, потому что нет ниоткуда, из чего можно было бы сделать вывод о жизни. Просто добавьте именованный параметр времени жизни к определяющему типу:

struct ObservableList<'a, T> {
    items: Vec<T>,
    on_changed: Event<OnChangedArgs<'a, T>>,
}

Это хорошо, потому что ясно показывает, что ObservableList содержит заимствование.


Решено

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

use std::sync::Arc;

type Callback<Args> = Arc<dyn Fn(&<Args as EventArgs>::Gatified<'_>)>;

trait EventArgs {
    type Gatified<'a>;
}

struct Event<Args: EventArgs> {
    handlers: Vec<Callback<Args>>,
}

impl<Args: EventArgs> Event<Args> {
    fn trigger(&self, args: &Args::Gatified<'_>) {
        for handler in &self.handlers {
            handler(args);
        }
    }
}

struct OnChangedArgs<'a, T> {
    items: std::slice::Iter<'a, T>,
}

impl<T: 'static> EventArgs for OnChangedArgs<'static, T> {
    type Gatified<'a> = OnChangedArgs<'a, T>;
}

struct ObservableList<T: 'static> {
    items: Vec<T>,
    on_changed: Event<OnChangedArgs<'static, T>>,
}

impl<T: 'static> ObservableList<T> {
    fn notify(&self) {
        self.on_changed.trigger(&OnChangedArgs {
            items: self.items.iter(),
        });
    }
}

OnChangedArgs<'static> служит типом маркера (это может быть и другой тип), который позволяет GATифицировать любой OnChangedArgs.