Общая проблема с истечением срока службы 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, чтобы видеть, что она может скрывать время жизни. Таким образом, вам не придется излишне увеличивать время жизни, чтобы вам приходилось иметь дело с ним выше в другой структуре.

Почему вы думаете, что жизнь ненужна? Я не могу понять. Кроме того, вполне вероятно, что вам нужна жизнь с более высоким рейтингом, а не конкретная (но я не могу сказать этого только по этому коду).

Chayim Friedman 27.07.2024 19:59

@ChayimFriedman Когда вы явно пишете Arc<dyn Fn(&OnChangedArgs<T>)>, это автоматически заменяет время жизни до времени, когда Fn вызывается. В случае общего type Callback<Args> = Arc<dyn Fn(&Args)>; struct Event<Args> { handler : Callback<Args>} код Event<OnChangedArgs<T>> недействителен, поскольку в нем отсутствует параметр времени жизни. Если бы элейдер мог видеть сквозь Event<Args>, он бы видел, что handler был бы эквивалентен Arc<dyn Fn(&OnChangedArgs<T>)>, и мог бы видеть соответственно.

Jacob Green 28.07.2024 23:18

Это «исключение» называется жизнью более высокого ранга, и вы можете сделать то же самое, но это будет довольно запутанно.

Chayim Friedman 28.07.2024 23:23

@ChayimFriedman Спасибо! Я видел отрывки из этого, но не был уверен, как это называется. На данный момент я просто по умолчанию использую 'static, а затем transmute, чтобы вернуться к правильному времени жизни, но надеюсь, что смогу переключиться на время жизни с более высоким рейтингом, когда смогу это понять.

Jacob Green 29.07.2024 00:12
Как создавать пользовательские общие типы в Python (50/100 дней Python)
Как создавать пользовательские общие типы в Python (50/100 дней Python)
Помимо встроенных типов, модуль типизации в Python предоставляет возможность определения общих типов, что позволяет вам определять типы, которые могут...
1
4
81
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

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

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

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

Jacob Green 26.07.2024 22:36

@JacobGreen Если он ничего не заимствует, вы можете использовать OnChangedArgs<'static, T>. Однако обратите внимание, что существует большая разница между «одолжить OnChangedArgs» и «взять что-то взаймы». Если OnChangedArgs имеет параметр времени жизни, то последнее верно, но вы говорите о первом.

cdhowie 26.07.2024 22:38
Ответ принят как подходящий

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

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.

Спасибо! Я только что еще раз взглянул на это, используя этот подход. Мне удалось полностью исключить использование 'static, используя два слоя GAT. Один для аргументов события, а другой для того, чтобы разные наблюдаемые коллекции могли использовать разные итераторы для уведомления о событии.

Jacob Green 31.07.2024 21:49

Другие вопросы по теме