Rust: Проблема с отслеживанием для `ops :: Try` (функция` try_trait`)

Созданный на 31 мая 2017  ·  99Комментарии  ·  Источник: rust-lang/rust

Признак Try из https://github.com/rust-lang/rfcs/pull/1859; реализовано в PR https://github.com/rust-lang/rust/pull/42275.

Отделите от https://github.com/rust-lang/rust/issues/31436 для ясности (согласно https://github.com/rust-lang/rust/pull/42275#discussion_r119167966)

  • [] Стабилизация позволит людям реализовать Iterator::try_fold

    • [] В рамках стабилизации повторно откройте PR # 62606, чтобы задокументировать реализацию try_fold для итераторов.

    • [] Убедитесь, что реализации других вещей по умолчанию имеют желаемый долгосрочный DAG, поскольку изменить их позже практически невозможно. (В частности, было бы неплохо, если бы fold было реализовано в терминах try_fold , чтобы не нужно было переопределять оба значения.)

A-error-handling B-RFC-implemented B-unstable C-tracking-issue Libs-Tracked T-lang T-libs

Самый полезный комментарий

Каков текущий статус этой функции?

Все 99 Комментарий

Пара штук велосипедных навесов:

  • Есть ли у нас особая мотивация называть связанный тип Error вместо Err ? Назвав его Err будет совпадать с Result : другой уже называется Ok .

  • Есть ли у нас особая мотивация иметь отдельные методы from_error и from_ok вместо одного from_result который был бы более симметричным с into_result который является другим? половина черты?

( обновленная версия ссылки на игровую площадку из RFC )

@glaebhoerl

  • Error vs Err обсуждалось относительно TryFrom в https://github.com/rust-lang/rust/issues/33417#issuecomment -269108968 и https: // github.com/rust-lang/rust/pull/40281; Я предполагаю, что это имя было выбрано здесь по аналогичным причинам.
  • Я считаю, что они разделены, потому что они используются по-разному, и я ожидаю, что очень редко кто-то действительно имеет Result который они пытаются превратить в T:Try . Я предпочитаю Try::from_ok и Try::from_error всегда вызывать Try::from_result(Ok( и Try::from_result(Err( , и я счастлив просто использовать эти два метода вместо записи совпадения. Возможно, это потому, что я думаю о into_result не как о Into<Result> , а как о «пройдено это или нет?», А конкретный тип - Result как неважная деталь реализации. (Я не хочу предлагать или повторно открывать «должен быть новый тип для производственной ценности и раннего возврата».) А что касается документации, мне нравится, что from_error говорит о ? (или, в конечном итоге, throw ), а from_ok говорит об успешной упаковке (# 41414), а не об их обоих в одном методе.

Я не уверен, что это правильный форум для этого комментария, пожалуйста, перенаправьте меня, если это не так: smiley :. Возможно, это должно было быть на https://github.com/rust-lang/rfcs/pull/1859, и я пропустил период комментариев; ой!


Мне было интересно, можно ли разделить черту Try или удалить метод into_result ; для меня в настоящее время Try чем-то напоминает сумму признаков FromResult (содержащих from_error и from_ok ) и IntoResult (содержащих into_result ).

FromResult обеспечивает очень эргономичный ранний выход с помощью оператора ? , что, на мой взгляд, является убийственным вариантом использования этой функции. Я думаю, что IntoResult уже можно аккуратно реализовать с помощью методов для каждого варианта использования или как Into<Result> ; мне не хватает полезных примеров?

Следуя RFC для Try trait , expr? можно десахарировать как:

match expr { // Removed `Try::into_result()` here.
    Ok(v) => v,
    Err(e) => return Try::from_error(From::from(e)),
}

Я рассмотрел мотивирующие примеры Future и Option .

Будущее

Мы можем реализовать FromResult естественно для struct FutureResult: Future как:

impl<T, E> FromResult for FutureResult {
    type Ok = T;
    type Error = E;
    fn from_error(v: Self::Error) -> Self {
        future::err(v)
    }
    fn from_ok(v: Self::Ok) -> Self {
        future::ok(v)
    }
}

Предполагая разумную реализацию для ? , которая вернется из impl Future оцениваемой функции при применении к Result::Err тогда я могу написать:

fn async_stuff() -> impl Future<V, E> {
    let t = fetch_t();
    t.and_then(|t_val| {
        let u: Result<U, E> = calc(t_val);
        async_2(u?)
    })
}
fn fetch_t() -> impl Future<T, E> {}
fn calc(t: T) -> Result<U,E> {}

Это именно то, что я пытался реализовать ранее сегодня, и Try прибивает! Но если мы попытаемся реализовать текущую Try для Future возможно, канонического выбора для into_result ; может быть полезно паниковать, блокировать или опрашивать один раз, но ни один из них не кажется универсальным. Если на Try не было into_result я могу реализовать ранний выход, как указано выше, и если мне нужно преобразовать Future в Result (а затем в любой Try ) Я могу преобразовать его подходящим методом (вызвать метод wait для блокировки, вызвать какой-нибудь poll_once() -> Result<T,E> и т. д.).

Вариант

Option - аналогичный случай. Мы реализуем from_ok , from_err естественным образом, как в RFC Try trait , и можем преобразовать Option<T> в Result<T, Missing> просто с помощью o.ok_or(Missing) или удобный метод ok_or_missing .


Надеюсь, это поможет!

Возможно, я очень опаздываю на все это, но в эти выходные мне пришло в голову, что ? имеет довольно естественную семантику в тех случаях, когда вы хотите замкнуть _success_.

fn fun() -> SearchResult<Socks> {
    search_drawer()?;
    search_wardrobe()
}

Но в данном случае не подходит именование методов трейтов Try .

Однако это расширило бы смысл оператора ? .

При создании прототипа try_fold для вискозы я обнаружил, что мне нужно что-то вроде Try::is_error(&self) для методов Consumer::full() . (Это есть в Rayon, потому что потребители в основном являются итераторами push-стиля, но мы все еще хотим устранять ошибки короткого замыкания.) Вместо этого мне пришлось сохранить промежуточные значения как Result<T::Ok, T::Err> чтобы я мог вызвать Result::is_err() .

Оттуда я также хотел, чтобы Try::from_result вернулся к T в конце, но match сопоставление с T::from_ok и T::from_error не слишком плохо.

Черта Try может предоставить эргономичный метод from_result если требуются from_ok и from_error . Или наоборот. Из приведенных примеров я вижу возможность предложить и то, и другое.

Поскольку PR # 42275 был объединен, означает ли это, что проблема решена?

@skade Обратите внимание, что тип может определять то, что является короткозамкнутым, как LoopState для некоторых внутренних компонентов Iterator . Возможно, это было бы более естественно, если бы в Try говорилось о «продолжении или прерывании», а не об успехе или неудаче.

@cuviper Версия, которая мне в итоге понадобилась, была либо типа Ok либо типа Error -in-original. Я надеюсь, что деструктуризация и перестройка - это достаточно общая вещь, которая будет хорошо оптимизирована, и куча специальных методов для этого трейта не понадобится.

@ErichDonGubler. Это проблема отслеживания, поэтому она не решается, пока соответствующий код не станет стабильным.

Отчет об опыте работы:

Я был немного разочарован, пытаясь применить эту черту на практике. Несколько раз я испытывал искушение определить свой собственный вариант для Result по какой-то причине, но каждый раз я заканчивал просто использованием Result в конце, в основном из-за реализации Try слишком раздражает. Я не совсем уверен, хорошо это или нет!

Пример:

В новом решателе для Chalk VM я хотел иметь перечисление, которое указывает результат решения «цепочки». У этого было четыре возможности:

enum StrandFail<T> {
    Success(T),
    NoSolution,
    QuantumExceeded,
    Cycle(Strand, Minimums),
}

Я хотел, чтобы ? применительно к этому перечислению разворачивало "успех", но распространял все остальные неудачи вверх. Однако, чтобы реализовать черту Try , мне пришлось бы определить своего рода «остаточный» тип, который инкапсулирует только случаи ошибок:

enum StrandFail {
    NoSolution,
    QuantumExceeded,
    Cycle(Strand, Minimums),
}

Но как только у меня появится этот тип, я могу сделать StrandResult<T> псевдонимом:

type StrandResult<T> = Result<T, StrandFail>;

и это то, что я сделал.

Это не обязательно кажется хуже, чем наличие одного перечисления, но это действительно кажется немного странным. Обычно, когда я пишу документацию для функции, например, я не «группирую» результаты по «ок» и «ошибка», а говорю о различных возможностях как о «равных». Например:

    /// Invoked when a strand represents an **answer**. This means
    /// that the strand has no subgoals left. There are two possibilities:
    ///
    /// - the strand may represent an answer we have already found; in
    ///   that case, we can return `StrandFail::NoSolution`, as this
    ///   strand led nowhere of interest.
    /// - the strand may represent a new answer, in which case it is
    ///   added to the table and `Ok` is returned.

Обратите внимание, что я не сказал «мы возвращаем Err(StrandFail::NoSolution) . Это потому, что Err просто ощущается как раздражающий артефакт, который я должен добавить.

(С другой стороны, текущее определение поможет читателям узнать, как ведет себя ? , не обращаясь к Try impl.)

Думаю, такой результат не так уж и удивителен: текущий Try impl заставляет вас использовать ? на вещах, изоморфных Result . Это единообразие не случайно, но, как следствие, раздражает использование ? с вещами, которые не являются "просто Result ". (В этом отношении, по сути, это та же проблема, которая порождает NoneError - необходимость искусственно определять тип, представляющий «отказ» Option .)

Я также отмечу, что в случае StrandFail я не особо хочу преобразование From::from которое получают обычные результаты, хотя мне это не мешает.

Является ли связанный тип Try::Error когда-либо доступным / использованным пользователем напрямую? Или это просто необходимо как часть обессахаривания ? ? В последнем случае я не вижу реальной проблемы в том, чтобы просто определить это «структурно» - type Error = Option<Option<(Strand, Minimums)>> или что-то в этом роде. (Необходимость вычислить структурный эквивалент «половины отказа» определения enum невелика, но кажется менее раздражающей, чем необходимость переделывать весь общедоступный API.)

Я тоже не слежу. Я успешно реализовал Try для типа с представлением внутренней ошибки, и мне казалось естественным, что Ok и Error относятся к одному и тому же типу. Вы можете увидеть реализацию здесь: https://github.com/kevincox/ecl/blob/8ca7ad2bc4775c5cfc8eb9f4309b2666e5163e02/src/lib.rs#L298 -L308

На самом деле кажется, что существует довольно простой шаблон для выполнения этой работы.

impl std::ops::Try for Value {
    type Ok = Self;
    type Error = Self;

    fn from_ok(v: Self::Ok) -> Self { v }
    fn from_error(v: Self::Error) -> Self { v }
    fn into_result(self) -> Result<Self::Ok, Self::Error> {
        if self.is_ok() { Ok(val) } else { Err(val) }
    }
}

Если вы хотите развить успех, должно сработать что-то вроде этого:

impl std::ops::Try for StrandFail<T> {
    type Ok = T;
    type Error = Self;

    fn from_ok(v: Self::Ok) -> Self { StrandFail::Success(v) }
    fn from_error(v: Self::Error) -> Self { v }
    fn into_result(self) -> Result<Self::Ok, Self::Error> {
        match self {
            StrandFail::Success(v) => Ok(v),
            other => Err(other),
        }
    }
}

Определение структурного типа возможно, но это довольно неприятно. Я согласен, что могу использовать Self . Мне кажется немного странным, что вы можете затем использовать ? для преобразования из StrandResult в Result<_, StrandResult> и т. Д.

Отчет об отличном опыте, @nikomatsakis! Еще я был недоволен Try (с другой стороны).

TL / DR : Я думаю, что FoldWhile понял это правильно, и мы должны удвоить внимание на интерпретацию Break -vs- Continue ? вместо того, чтобы говорить о это с точки зрения ошибок.

Дольше:

Я продолжаю использовать Err для чего-то более близкого к "успеху", чем "ошибка", потому что ? очень удобно.

Итак, я считаю, что описание, которое я написал для Try , неверно и не должно говорить о «дихотомии успеха / неудачи», а скорее абстрагироваться от «ошибок».

Еще я подумал о том, что мы должны рассмотреть несколько безумных имплсов для Try . Например, Ordering: Try<Ok = (), Error = GreaterOrLess> сочетании с "пробными функциями" может позволить cmp для struct Foo<T, U> { a: T, b: U } просто быть

fn cmp(&self, other: &self) -> Ordering try {
    self.a.cmp(&other.a)?;
    self.b.cmp(&other.b)?;
}

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

Я также отмечаю, что это еще один случай, когда «преобразование ошибок» бесполезно. Я также никогда не использовал его в приведенных выше примерах «Я хочу? Но это не ошибка». И, конечно, известно, что это вызывает грусть вывода, поэтому мне интересно, должно ли это быть ограничено только Результатом (или потенциально удалено в пользу .map_err(Into::into) , но это, вероятно, невозможно).

Изменить: О, и все, что заставляет меня задаться вопросом, возможно, ответ на «Я продолжаю использовать Результат для своих ошибок вместо реализации Try для своего собственного типа» - это «хорошо, это ожидалось».

Изменить 2: это мало чем отличается от https://github.com/rust-lang/rust/issues/42327#issuecomment -318923393 выше

Изменить 3: похоже, что вариант Continue также был предложен в https://github.com/rust-lang/rfcs/pull/1859#issuecomment -273985250

Мои два цента:

Мне нравится предложение @fluffysquirrels разделить черту на две черты. Один для преобразования в результат, а другой для преобразования из результата. Но я действительно думаю, что мы должны оставить into_result или эквивалент как часть обессахаривания. И я думаю, что на данный момент мы должны это сделать, поскольку использование Option в качестве Try стабилизировалось.

Мне также нравится идея @scottmcm об использовании имен, которые предполагают break / continue, а не error / ok.

Чтобы вставить здесь конкретный код, мне нравится, как это читается:

https://github.com/rust-lang/rust/blob/ab8b961677ac5c74762dcea955aa0ff4d7fe4915/src/libcore/iter/iterator.rs#L1738 -L1746

Это, конечно, не так хорошо, как прямой цикл, но с «методами попытки» он был бы близок:

self.try_for_each(move |x| try { 
    if predicate(&x) { return LoopState::Break(x) } 
}).break_value() 

Для сравнения, я считаю, что версия "словаря ошибок" действительно вводит в заблуждение:

self.try_for_each(move |x| { 
    if predicate(&x) { Err(x) } 
    else { Ok(()) } 
}).err() 

Можем ли мы реализовать Display for NoneError? Это позволит ящику с ошибкой автоматически получить From<NoneError> for failure::Error . См. Https://github.com/rust-lang-nursery/failure/issues/61
Это должно быть изменение на 3 строки, но я не уверен в процессе RFC и тому подобном.

@ cowang4 Я бы хотел попытаться избежать включения какого-либо смешивания Result-and-Option прямо сейчас, так как этот тип нестабилен в основном для того, чтобы наши варианты оставались открытыми. Не удивлюсь, если в итоге мы изменим дизайн Try и десахара на что-то, что не требует NoneError ...

@scottmcm Хорошо. Я понимаю вашу точку зрения. В конце концов, я бы хотел получить чистый способ приукрасить шаблон возврата Err, когда библиотечная функция возвращает None. Может быть, вы знаете кого-то другого, кроме Try ? Пример:

fn work_with_optional_types(pb: &PathBuf) -> Result<MyStruct, Error> {
    if let Some(filestem) = pb.file_stem() {
        if let Some(filestr) = filestem.to_str() {
            return Ok(MyStruct {
                filename: filestr.to_string()
            });
        }
     }
    Err(_)
}

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

use failure::Error;
fn work_with_optional_types(pb: &PathBuf) -> Result<MyStruct, Error> {
    Ok({
        title: pb.file_stem?.to_str()?.to_string()
    })
}

Что _почти_ работает, за исключением отсутствия impl Display for NoneError как я упоминал ранее.
Но, если это не тот синтаксис, который мы хотели бы использовать, тогда, возможно, могла бы быть другая функция / макрос, упрощающий шаблон:

if option.is_none() {
    return Err(_);
}

@ cowang4 Я считаю, что это сработает, если вы реализовали From<NoneError> для failure::Error , который использовал ваш собственный тип, реализующий Display .

Однако, вероятно, лучше использовать opt.ok_or(_)? чтобы вы могли явно указать, какой должна быть ошибка, если для параметра Option установлено значение None. В вашем примере, например, вам может потребоваться другая ошибка, если pb.file_stem равно None, чем если to_str() возвращает None.

@tmccombs Я попытался создать свой собственный тип ошибки, но, должно быть, сделал это неправильно. Это было так:

#[macro_use] extern crate failure_derive;

#[derive(Fail, Debug)]
#[fail(display = "An error occurred.")]
struct SiteError;

impl From<std::option::NoneError> for SiteError {
    fn from(_err: std::option::NoneError) -> Self {
        SiteError
    }
}

fn build_piece(cur_dir: &PathBuf, piece: &PathBuf) -> Result<Piece, SiteError> {
    let title: String = piece
        .file_stem()?
        .to_str()?
        .to_string();
    Ok(Piece {
        title: title,
        url: piece
            .strip_prefix(cur_dir)?
            .to_str()
            .ok_or(err_msg("tostr"))?
            .to_string(),
    })
}

А потом я попытался использовать свой тип ошибки ...

error[E0277]: the trait bound `SiteError: std::convert::From<std::path::StripPrefixError>` is not satisfied
   --> src/main.rs:195:14
    |
195 |           url: piece
    |  ______________^
196 | |             .strip_prefix(cur_dir)?
    | |___________________________________^ the trait `std::convert::From<std::path::StripPrefixError>` is not implemented for `SiteError`
    |
    = help: the following implementations were found:
              <SiteError as std::convert::From<std::option::NoneError>>
    = note: required by `std::convert::From::from`

error[E0277]: the trait bound `SiteError: std::convert::From<failure::Error>` is not satisfied
   --> src/main.rs:195:14
    |
195 |           url: piece
    |  ______________^
196 | |             .strip_prefix(cur_dir)?
197 | |             .to_str()
198 | |             .ok_or(err_msg("tostr"))?
    | |_____________________________________^ the trait `std::convert::From<failure::Error>` is not implemented for `SiteError`
    |
    = help: the following implementations were found:
              <SiteError as std::convert::From<std::option::NoneError>>
    = note: required by `std::convert::From::from`

Хорошо, я только что понял, что он хочет знать, как преобразовать из других типов ошибок в мою ошибку, которую я могу написать в общем виде:

impl<E: failure::Fail> From<E> for SiteError {
    fn from(_err: E) -> Self {
        SiteError
    }
}

Неа...

error[E0119]: conflicting implementations of trait `std::convert::From<SiteError>` for type `SiteError`:
   --> src/main.rs:183:1
    |
183 | impl<E: failure::Fail> From<E> for SiteError {
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = note: conflicting implementation in crate `core`:
            - impl<T> std::convert::From<T> for T;

Хорошо, а как насчет std::error::Error ?

impl<E: std::error::Error> From<E> for SiteError {
    fn from(_err: E) -> Self {
        SiteError
    }
}

Это тоже не работает. Отчасти потому, что он конфликтует с моим From<NoneError>

error[E0119]: conflicting implementations of trait `std::convert::From<std::option::NoneError>` for type `SiteError`:
   --> src/main.rs:181:1
    |
175 | impl From<std::option::NoneError> for SiteError {
    | ----------------------------------------------- first implementation here
...
181 | impl<E: std::error::Error> From<E> for SiteError {
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `SiteError`
    |
    = note: upstream crates may add new impl of trait `std::error::Error` for type `std::option::NoneError` in future versions

Это странно, потому что я думал, что NoneError не реализует std::error::Error . Когда я комментирую свои необщие impl From<NoneError> я получаю:

error[E0277]: the trait bound `std::option::NoneError: std::error::Error` is not satisfied
   --> src/main.rs:189:25
    |
189 |       let title: String = piece
    |  _________________________^
190 | |         .file_stem()?
    | |_____________________^ the trait `std::error::Error` is not implemented for `std::option::NoneError`
    |
    = note: required because of the requirements on the impl of `std::convert::From<std::option::NoneError>` for `SiteError`
    = note: required by `std::convert::From::from`

Надо ли писать все From вручную. Я думал, что их должен был доставить ящик отказа?

Может мне стоит придерживаться option.ok_or()

Надо ли писать все From вручную. Я думал, что их должен был доставить ящик отказа?

Я не думаю, что это делает ящик отказа. Но я мог ошибаться.

Хорошо, поэтому я повторно проверил ящик с ошибкой, и, если я правильно читаю документацию и разные версии, он предназначен для всегда использования failure::Error в качестве типа ошибки в вашем Result s, см. здесь . И он реализует общую черту impl Fail для большинства типов ошибок:

impl<E: StdError + Send + Sync + 'static> Fail for E {}

https://github.com/rust-lang-nursery/failure/blob/master/failure-1.X/src/lib.rs#L218

И затем impl From чтобы он мог Try / ? другие ошибки (например, из std) в общий тип failure::Error .
rust impl<F: Fail> From<F> for ErrorImpl
https://github.com/rust-lang-nursery/failure/blob/d60e750fa0165e9c5779454f47a6ce5b3aa426a3/failure-1.X/src/error/error_impl.rs#L16

Но дело в том, что поскольку ошибка, относящаяся к этой проблеме ржавчины, NoneError , является экспериментальной, она еще не может быть преобразована автоматически, потому что она не реализует черту Display . И мы этого не хотим, потому что это стирает грань между Option s и еще Result s. В конечном итоге все это, вероятно, будет переработано и разобрано, но пока я буду придерживаться техник без сахара, которым научился.

Спасибо всем за помощь. Я потихоньку изучаю Rust! :улыбка:

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

: +1: Честно говоря, я думаю, что .ok_or(...)? и останется (или, конечно, .ok_or_else(|| ...)? ). Даже если NoneError _had_ a Display impl, что он скажет? «Чего-то там не было»? Это не большая ошибка ...

Попытка приблизиться к конкретному предложению ...

Мне начинает нравиться альтернатива TrySuccess . Что интересно, я думаю, что _nobody_, в том числе и я, изначально понравился этот вариант - его даже нет в окончательной версии RFC. Но, к счастью, он живет в истории: https://github.com/rust-lang/rfcs/blob/f89568b1fe5db4d01c4668e0d334d4a5abb023d8/text/0000-try-trait.md# using -an-associated-type-for-the-success -ценить

Мне кажется, что самым большим возражением против этого была обоснованная жалоба на то, что целая основная черта только для связанного типа ( trait TrySuccess { type Success; } ) была излишней. Но с возвращением блоков catch try (https://github.com/rust-lang/rust/issues/41414#issuecomment-373985777) для выполнения оклейки (как в RFC) , внезапно у него есть прямое и важное применение: это черта, которая контролирует эту упаковку. В сочетании с целью « ? всегда должны приводить к одному и тому же типу», что, как мне кажется, было желательным, эта черта, кажется, лучше удерживает свой вес. Строуман:

trait TryContinue {
    type Continue;
    fn from_continue(_: Self::Continue) -> Self;
}

Этот связанный тип, который будет возвращен оператором ? , также явно должен существовать. Это означает, что он не попадает в раздражение «должен был определить своего рода« остаточный »тип», который сформулировал Нико . И быть () разумно, даже часто, поэтому избегайте искажений, подобных NoneError .

Изменить: для велоспорта «возврат» может быть хорошим словом, так как это то, что происходит, когда вы return из метода try (также известного как ok-wrapping). return - это также имя оператора монады для этого, iiuc ...

Изменить 2: Мои мысли о других чертах / методах еще не стабилизировались, @tmccombs.

@scottmcm для ясности, или вы предлагаете следующие две черты?

trait TryContinue {
  type Continue;
  fn from_continue(_: Self::Continue) -> Self;
}

trait Try<E>: TryContinue {
  fn try(self) -> Result<Self::Continue, E>
}

И удаление x? будет выглядеть примерно так:

x.try() match {
    Ok(c) => c,
   Err(e) => throw e // throw here is just a placeholder for either returning or breaking out of a try block
}

и try { ...; expr} десахарирует до чего-то вроде:

{ 
    ...
    TryContinue::from_continue(expr);
}

@scottmcm Я тоже считаю этот вариант более привлекательным, если учесть ок-обертку =)

Переходя к следующему признаку, я думаю, что для ? будет следующим (по модулю массового байкшеддинга):

trait Try<Other: TryContinue = Self>: TryContinue + Sized {
    fn check(x: Other) -> ControlFlow<Other::Continue, Self>;
}

enum ControlFlow<C, B> {
    Continue(C),
    Break(B),
}

Разное обоснование:

  • Для этого требуется TryContinue в качестве типа аргумента, чтобы тип выражения x? всегда был одинаковым.
  • По умолчанию параметр типа равен Self для простых случаев, таких как try_fold которые проверяют и возвращают один и тот же тип, поэтому все в порядке с T: Try .
  • Эта черта расширяет TryContinue потому что, если тип, используемый в качестве возвращаемого типа, допускает ? в своем теле, он также должен поддерживать ok-wrapping.
  • Новое перечисление изоморфно результату, но избегает использования терминов «ошибки», как обсуждалось выше, и в результате я думаю, что оно обеспечивает очень четкое значение для каждого из вариантов.
  • Аргумент ? является универсальным типом, так что, как и TryContinue , он "производит Self из чего-то"

Полная демонстрация концепции, включая макросы для try{} / ? и имплицитные для Option , Result и Ordering : https : //play.rust-lang.org/? gist = 18663b73b6f35870d20fd172643a4f96 & version = stable (спасибо @nikomatsakis за создание оригинала год назад 🙂)

Минусы:

  • В Result все еще есть параметры фантомного типа, которые мне не очень нравятся

    • Но, возможно, это хороший компромисс, поскольку нет необходимости в дополнительном типе LessOrGreater в Ordering impl.

    • И в любом случае параметры фантомного типа в impls менее неприятны, чем в типах

  • Не дает последовательного преобразования From

    • Но это может быть хорошо, поскольку StrandResult любом случае не заботится, для чего-то вроде SearchResult это было бы странно, поскольку путь Break - это путь успеха, и это может в конечном итоге помочь в выводе в в любом случае вложенные кейсы ? .

  • Не так очевидно, каким будет синтаксис throw

    • Что теряет объяснение ? как Ok(x) => x, Err(r) => throw e.into() которое мне действительно понравилось

    • Но также можно позволить throw; быть способом получения None (через что-то вроде impl<T> Throw<()> for Option<T> ), что намного лучше, чем throw NoneError;

    • И разделение этого в любом случае может быть хорошим, поскольку throw LessOrGreater::Less было бы _действительно_ глупо.

Изменить: Ping @glaebhoerl , чье мнение я хотел бы по этому

Изменить 2: Также cc @ colin-kiegel для этого утверждения из https://github.com/rust-lang/rfcs/pull/1859#issuecomment -287402652:

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

Мне очень нравится это предложение.

Не так очевидно, какой будет синтаксис throw

Игнорируя багаж ключевого слова throw из других языков, слово «throw» имеет смысл, поскольку вы «перебрасываете» значение в более высокую область видимости. Python и scala также используют исключения для потока управления, кроме случаев ошибок (хотя в случае scala вы обычно не используете try / catch напрямую), поэтому есть некоторый прецедент использования throw для успешных путей.

@tmccombs

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

Хотя в нем такой же багаж, я подозреваю, что "поднять" лучше:

throw (v): положить или заставить пойти или войти в какое-то место, положение, состояние и т. д., как будто бросая:

Raise (v): перейти на более высокую позицию; поднимать; поднять

Может быть способ объединить «рейз» с логикой ? , учитывая, что это повышение может также означать «собирать». Что-то вроде: Ok(v) => v, Err(e) => raise From::from(e) , где raise имитирует совпавший шаблон (например, для шаблона Err(e) это синтаксическая магия для ControlFlow::Break(Err(...)) ).

Я подозреваю, что "рейз" больше подходит

хорошая точка зрения

@scottmcm

Не так очевидно, какой будет синтаксис throw

Есть ли причина, по которой у нас не может быть from_err(value: Other) ?

ОБНОВЛЕНИЕ: Возможно, я запутался в роли Other самом деле. Мне нужно еще немного изучить эту черту. знак равно

@nikomatsakis

Есть ли причина, по которой у нас не может быть from_err(value: Other) ?

Ну, Other - это целый тип ? -able (например, Result ), поэтому я бы не хотел, чтобы throw Ok(4) работал. (И это должно быть полное разделение, чтобы избежать принудительного введения искусственного типа ошибки.) Например, я думаю, что наше текущее взаимодействие Option-Result будет эквивалентно этому (и обратному):

impl<T, F, U> Try<Option<U>> for Result<T, F>
   where F: From<NoneError>
{
    fn check(x: Option<U>) -> ControlFlow<U, Self> {
        match x {
            Some(x) => ControlFlow::Continue(x),
            None => ControlFlow::Break(Err(From::from(NoneError))),
        }
    }
}

Моя текущая склонность к throw была бы такой:

trait TryBreak<T> : TryContinue { from_break(_: T) -> Self }

с участием

throw $expr  ==>  break 'closest_catch TryBreak::from_break($expr)
  • Расширяет TryContinue так что ok-wrapping также доступен для возвращаемого типа метода, в котором он используется.
  • Не связанный тип, поэтому вы могли бы, если хотите, использовать как TryBreak<TryFromIntError> и TryBreak<io::Error> для типа.

    • и impl<T> TryBreak<()> for Option<T> чтобы включить только throw; кажется правдоподобным, в отличие от связанного типа ошибки () .

    • impl<T> TryBreak<!> for T тоже было бы неплохо, но, вероятно, бессвязно.

(В сторону: эти черты и имена методов ужасны; пожалуйста, помогите !)

Мои мысли о других проблемах, поднятых здесь, еще не превратились в легко формулируемую форму, но в отношении проблем вывода типа вокруг десугарирования From::from() (я не могу вспомнить, обсуждалось ли это также где-то в другом месте в последнее время , или только здесь?):

Инстинктивное ощущение (из опыта Haskell), что «такая неоднозначность вывода типа лжи» было одной из (хотя и не главной) причин, по которым я не хотел иметь преобразование From как часть исходного RFC. Теперь, когда он запекся в пирог, мне интересно, не могли бы мы попробовать получить этот пирог и съесть его, просто «просто» добавив для него специальное правило по умолчанию в процесс определения типа?

То есть, я думаю: всякий раз, когда мы видим From::from() [необязательно: тот, который был вставлен с помощью удаления сахара ? , а не написан вручную], и мы точно знаем один из двух типов (ввод vs. output), в то время как другой неоднозначен, мы по умолчанию используем другое значение, аналогичное первому. Другими словами, я думаю, когда неясно, какой impl From использовать, мы по умолчанию используем impl<T> From<T> for T . Думаю, это всегда то, чего вы на самом деле хотите? Это может быть немного спонтанно, но если это действительно сработает, преимущества, похоже, окупаются ИМХО.

(Я также думал, что From уже является элементом языка, именно из-за удаления сахара ? , но похоже, что это не так? В любом случае, это уже в некотором роде особенное по этой причине .)

в предложении @scottmcm From::from() _не_ часть процесса обессахаривания, скорее это реализация Try для Result .

@tmccombs Я не предлагал поправку к его предложению.

В try{} RFC (https://github.com/rust-lang/rfcs/pull/2388) обсуждаются блоки try где результат не важен. Эта альтернатива, кажется, справляется с этим прилично, поскольку она может полностью игнорировать типы ошибок, если того пожелает, позволяя

let IgnoreErrors = try {
    error()?;
    none()?;
};

Доказательная реализация с использованием тех же характеристик, что и раньше: https://play.rust-lang.org/?gist=e0f6677632e0a9941ed1a67ca9ae9c98&version=stable

Я думаю, что здесь есть некоторые интересные возможности, особенно потому, что пользовательская реализация может, скажем, принимать только результаты и связывать E: Debug поэтому она автоматически регистрирует любую возникающую ошибку. Или версия может быть специально предназначена как возвращаемый тип для main в сочетании с Termination который "просто работает", чтобы позволить вам использовать ? без сигнатуры сложного типа (https : //github.com/rust-lang/rfcs/issues/2367).

У меня были проблемы, аналогичные тем, о которых свидетельствует @nikomatsakis, использующий существующую версию Try . По конкретным вопросам см. Https://github.com/SergioBenitez/Rocket/issues/597#issuecomment -381533108.

Определения признаков, предложенные @scottmcm, решают эти проблемы. Однако они кажутся более сложными, чем необходимо. Я попытался реализовать их заново и придумал следующее:

#[derive(Debug, Copy, Clone)]
enum ControlFlow<C, B> {
    Continue(C),
    Break(B),
}

// Used by `try { }` expansions.
trait FromTry: Try {
    fn from_try(value: Self::Continue) -> Self;
}

// Used by `?`.
trait Try<T = Self>: Sized {
    type Continue;
    fn check(x: T) -> ControlFlow<Self::Continue, Self>;
}

Основное изменение заключается в том, что связанный с Continue тип относится к типу Try а не к FromTry (ранее TryContinue ). Помимо упрощения определений, это имеет то преимущество, что Try может быть реализовано независимо от FromTry , которое я считаю более распространенным, и что реализация FromTry упрощается после того, как Try Реализовано Try и FromTry были реализованы одновременно, мы можем просто переместить метод from_try в Try )

Посмотрите полную игровую площадку с реализациями для Result и Option а также Outcome Rocket на этой игровой площадке .

Спасибо за отчет, @SergioBenitez! Эта реализация соответствует альтернативной версии "перевернуть параметры типа" исходного предложения по признаку в RFC 1859: https://github.com/rust-lang/rfcs/blob/f89568b1fe5db4d01c4668e0d334d4a5abb023d8/text/0000-try-trait.md#unresolved -вопросов

Больше всего теряется то свойство, что typeof(x?) зависит только от typeof(x) . Отсутствие этого свойства было одной из проблем оригинала («Я немного беспокоюсь о читабельности кода в этих строках» https://github.com/rust-lang/rfcs/pull/1859#issuecomment-279187967) и преимуществом окончательного редукционистского предложения («Для любого данного типа T? может дать ровно один вид значения ok / error» https://github.com/rust-lang/rfcs/pull/1859#issuecomment-283104310). Конечно, были также аргументы в пользу того, что указанное свойство является ненужным или слишком ограничивающим, но в окончательном резюме до FCP оно все еще присутствовало в качестве преимущества (https://github.com/rust-lang/rfcs/pull/1859#issuecomment -295878466).

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

Конечно, сегодня from_ok встречается реже, поскольку Try и do catch нестабильны. И даже если бы do catch были стабильными, я согласен, что он будет использоваться менее ? целом (поскольку большинство таких блоков содержат несколько ? s).

Однако с точки зрения трейта и его операций, я думаю, что «обернуть значение« продолжать работу »в тип носителя» является абсолютно важной частью ? . Сегодня редко используют эту черту - просто вместо этого используют Ok(...) или Some(...) - но это критично для общего использования. Например, try_fold :

https://github.com/rust-lang/rust/blob/8728c7a726f3e8854f5a80b474d1a8bacab10304/src/libcore/iter/iterator.rs#L1478 -L1482

Если функция собирается возвращать тип носителя, очень важно, чтобы был способ поместить интересующее значение в этот тип носителя. И, что немаловажно, я не думаю, что это будет трудностью. У него очень естественное определение результата, варианта, результата и т. Д.

@Centril также ранее указывал, что "обернуть скаляр в носитель" также теоретически является более простой конструкцией. Например, Haskell называет это классом типов Pointed , хотя я не думаю, что мы хотим обобщать его _that_ далеко в Rust: разрешение try { 4 }vec![4] кажется мне излишним. .

Я также представляю себе будущее, в котором, подобно функциям async которые предлагаются для переноса значения блока в будущее, у нас могут быть функции try которые превращают значение блока в ошибочный тип. Опять же, TryContinue является критической частью - такая функция может даже не использовать ? , если у нас есть конструкция throw .

Так что все это, к сожалению, скорее философское, чем конкретное. Дайте мне знать, имеет ли это какой-то смысл или вы думаете об обратном в любой из частей: слегка_smiling_face:

Изменить : извиняюсь, если вы получили электронное письмо с ранней версией этого; Я нажал "комментарий" слишком рано ...

Больше всего теряется свойство, согласно которому typeof (x?) Зависит только от typeof (x).

Ах да, конечно. Спасибо что подметил это.

Конечно, были также аргументы в пользу того, что указанное свойство является ненужным или слишком ограничивающим, но в окончательном резюме до FCP оно все еще присутствовало в качестве преимущества (rust-lang / rfcs # 1859 (комментарий)).

Есть ли какие-то конкретные примеры, когда это может быть слишком ограничительным?

Если функция собирается возвращать тип носителя, очень важно, чтобы был способ поместить интересующее значение в этот тип носителя. И, что немаловажно, я не думаю, что это будет трудностью.

Я считаю, что это справедливый анализ. Я согласен.

@SergioBenitez Из https://github.com/rust-lang/rfcs/pull/1859#issuecomment -279187967

Итак, вопрос в том, достаточно ли предложенных ограничений? Есть ли хорошее использование контекста случая ошибки при определении типа успеха? Возможны ли нарушения?

По своему опыту работы с фьючерсами я могу сказать, что здесь могут быть некоторые полезные случаи. В частности, тип опроса, о котором вы говорите, можно обработать двумя способами. Иногда мы хотим отказаться от варианта NotReady и остаться с по существу значением Result. Иногда нас интересует только готовый вариант, и мы хотим перейти к любому из других вариантов (как в вашем скетче). Если мы позволим типу успеха частично определяться типом ошибки, более правдоподобно поддержать оба этих случая и в основном использовать контекст для определения желаемой декомпозиции.

OTOH, меня немного беспокоит читабельность кода в этих строках. Кажется, что это качественно отличается от простого преобразования компонента ошибки - это означает, что основное совпадение? будет зависеть от контекстной информации.

Таким образом, можно представить себе черту, которая перемещает _ оба_ типа в параметры типа, например

trait Try<T,E> {
    fn question(self) -> Either<T, E>;
}

И используйте это, чтобы включить все

let x: Result<_,_> = blah.poll()?; // early-return if NotReady
let x: Async<_> = blah.poll()?; // early-return if Error
let x: i32 = blah.poll()?; // early-return both NotReady and Errors

Но я думаю, что это определенно плохая идея, поскольку это означает, что они не работают.

println!("{}", z?);
z?.method();

Поскольку нет контекста типа, чтобы сказать, что производить.

Другая версия должна была бы включать такие вещи:

fn foo() -> Result<(), Error> {
    // `x` is an Async<i32> because NotReady doesn't fit in Result
    let x = something_that_returns_poll()?;
}
fn bar() -> Poll<(), Error> {
    // `x` is just i32 because we're in a Poll-returning method
    let x = something_that_returns_poll()?;
}

Мой инстинкт подсказывает, что вывод "вытекает из?" там слишком удивительно, а значит, это в «слишком умном» ведре.

Крайне важно, я не думаю, что его отсутствие является слишком ограничительным. my_result? в функции -> Poll не нуждается, так как тип успеха такой же, как обычно (важно, чтобы синхронный код работал одинаково в контекстах async), и вариант ошибки также преобразуется нормально . Использование ? на Poll в методе, возвращающем Result , в любом случае кажется анти-шаблоном, а не чем-то, что должно быть обычным, поэтому использование (гипотетических) выделенных методов, таких как .wait(): T (чтобы заблокировать результат) или .ready(): Option<T> (чтобы проверить, выполнено ли), чтобы выбрать режим, в любом случае, вероятно, лучше.

Это "интересно" https://play.rust-lang.org/?gist=d3f2cd403981a631f30eba2c3166c1f4&version=nightly&mode=debug

Мне не нравятся эти блоки try (do catch), они не кажутся очень удобными для новичков.

Я пытаюсь собрать текущее состояние предложения, которое, кажется, разбросано по нескольким комментариям. Есть ли единый обзор предлагаемого в настоящее время набора признаков (который отбрасывает связанный тип Error )?

В начале этой ветки я вижу комментарий о разделении Try на отдельные черты ошибок и от них - есть ли какие-либо планы по реализации этого разделения?

Было бы полезно иметь прозрачное преобразование из Result<T, E> в любой тип Other<T2, E> под вопросительным знаком - это позволило бы существующим функциям ввода-вывода вызываться с красивым синтаксисом из функции с более специализированной (например, ленивый) возвращаемый тип.

pub fn async_handler() -> AsyncResult<()> {
    let mut file = File::create("foo.txt")?;
    AsyncResult::lazy(move || {
        file.write_all(b"Hello, world!")?;
        AsyncResult::Ok(())
    })
}

Семантически это похоже на From::from(E) -> Other<T2, E> , но использование From в настоящее время ограничено существующей Result -эквивалентной реализацией Try .

Я действительно думаю, что у NoneError должна быть отдельная проблема с отслеживанием. Даже если признак Try никогда не стабилизируется, NoneError должен стабилизироваться, потому что это делает использование ? на Option гораздо более эргономичным. Учитывайте это в случае ошибок типа struct MyCustomSemanthicalError; или ошибок реализации Default . None можно легко преобразовать в MyCustomSeemanthicalError помощью From<NoneError> .

Работая над https://github.com/rust-analyzer/rust-analyzer/ , я встретил несколько отличающийся от недостатков в операторе ? , вырезанный из бумаги, особенно когда возвращаемый тип - Result<Option<T>, E> .

Для этого типа имеет смысл для ? эффективно десахарировать:

match value? {
    None => return Ok(None),
    Some(it)=>it,
}

где value имеет тип Result<Option<V>, E> , или:

value?

где value - это Result<V, E> . Я считаю, что это возможно, если стандартные библиотеки реализуют Try следующим образом для Option<T> , хотя я не тестировал это явно и думаю, что могут быть проблемы со специализацией.

enum NoneError<E> {
    None,
    Err(E),
}

impl From<T> for NoneError<T> {
    fn from(v: T) -> NoneError<T> {
        NoneError:Err(v)
    }
}

impl<T, E> std::Ops::Try for Result<Option<T>, E> {
    type OK = T;
    type Error = NoneError<E>;
    fn into_result(self) -> Result<Self::OK, Self::Error> {
        match self {
            Ok(option) => {
                if let Some(inner) = option {
                    Ok(inner)
                } else {
                    Err(NoneError::None)
                }
            }
            Err(error) => {
                Err(NoneError::Err(error));
            }
        }
    }
    fn from_error(v: Self::Error) -> Self {
        match v {
            NoneError::Err(error) => Err(error),
            None => Some(None),
        }
    }
    fn from_ok(v: Self::OK) -> Self {
        Ok(Some(v))
    }
}

impl<T> std::Ops::Try for Option<T> {
    type OK = T;
    type Error = NoneError<!>;
    fn into_result(self) -> Result<Self::OK, Self::Error> {
        match self {
            None => Err(NoneError::None),
            Some(v) => Ok(v),
        }
    }
    fn from_error(v: Self::Error) -> Self {
        match v {
            NoneError::None => Some(None),
            _ => unreachable!("Value of type ! obtained"),
        }
    }
    fn from_ok(v: Self::OK) -> Self {
        Ok(v)
    }
}

На вопрос @matklad, почему он не может создать настраиваемое перечисление, реализующее Try , которое в этом случае будет называться Cancellable , он указал, что std::ops::Try нестабильно, поэтому в любом случае нельзя использовать, учитывая, что rust-analyzer (в настоящее время) нацелен на стабильную ржавчину.

Сделайте репост с https://github.com/rust-lang/rust/issues/31436#issuecomment -441408288, потому что я хотел прокомментировать это, но я думаю, что это была неправильная проблема:

По сути, у меня есть ситуация обратного вызова в среде графического интерфейса - вместо того, чтобы возвращать Option или Result , им нужно вернуть UpdateScreen , чтобы сообщить платформе, если экран нужно обновлять или нет. Часто мне вообще не требуется протоколирование (просто нецелесообразно регистрировать каждую незначительную ошибку) и просто возвращать UpdateScreen::DontRedraw при возникновении ошибки. Однако с текущим оператором ? я должен писать это все время:

let thing = match fs::read(path) {
    Ok(o) => o,
    Err(_) => return UpdateScreen::DontRedraw,
};

Поскольку я не могу преобразовать Result::Err в UpdateScreen::DontRedraw помощью оператора Try, это становится очень утомительным - часто у меня есть простые поиски в хэш-картах, которые могут завершиться неудачно (что не является ошибкой. ) - так часто в одном обратном вызове у меня 5-10 использований оператора ? . Поскольку приведенное выше очень многословно для написания, мое текущее решение - это impl From<Result<T>> for UpdateScreen как это , а затем использовать внутреннюю функцию в обратном вызове следующим образом:

fn callback(data: &mut State) -> UpdateScreen {
     fn callback_inner(data: &mut State) -> Option<()> {
         let file_contents = fs::read_to_string(data.path).ok()?;
         data.loaded_file = Some(file_contents);
         Some(())
     }

    callback_inner(data).into()
}

Поскольку обратный вызов используется как указатель на функцию, я не могу использовать -> impl Into<UpdateScreen> (по какой-то причине возврат impl в настоящее время не разрешен для указателей на функции). Так что единственный способ для меня вообще использовать оператор Try - это проделать трюк с внутренней функцией. Было бы неплохо, если бы я мог просто сделать что-то вроде этого:

impl<T> Try<Result<T>> for UpdateScreen {
    fn try(original: Result<T>) -> Try<T, UpdateScreen> {
        match original {
             Ok(o) => Try::DontReturn(o),
             Err(_) => Try::Return(UpdateScreen::DontRedraw),
        }
    }
}

fn callback(data: &mut State) -> UpdateScreen {
     // On any Result::Err, convert to an UpdateScreeen::DontRedraw and return
     let file_contents = fs::read_to_string(data.path)?;
     data.loaded_file = Some(file_contents);
     UpdateScreen::Redraw
}

Я не уверен, возможно ли это с текущим предложением, и просто хотел добавить свой вариант использования для рассмотрения. Было бы здорово, если бы пользовательский оператор Try мог поддерживать что-то подобное.

@joshtriplett Извините, что https://github.com/rust-lang/rust/compare/master...scottmcm : try-trait-v2, чтобы быть конкретным. Я надеюсь попробовать еще кое-что с ним.

@scottmcm У вас есть более

@scottmcm FWIW Я тоже пробовал ваши изменения в районе:
https://github.com/rayon-rs/rayon/compare/master...cuviper : try-trait-v2
(по-прежнему используются частные копии, а не нестабильные элементы)

Так как же можно преобразовать Option NoneError? Похоже, что из-за того, что он реализует Try Trait, он не будет компилироваться, если вы не включите использование этой конкретной (нестабильной?) Функции.

В общем, вы не можете использовать? оператор с Option, насколько мне известно?

@omarabid , оператор стабилен для использования с Option или Result , но вы не можете использовать Try в качестве общего ограничения, пока оно не станет стабильным. Совершенно нормально использовать ? для Option в функции, возвращающей Option , поскольку вам вообще не нужно задействовать NoneError . Вы также можете вернуть Result если удалите типы:

use std::fmt::Debug;

pub struct Error(Box<dyn Debug>);

impl<T: Debug + 'static> From<T> for Error {
    fn from(error: T) -> Self {
        Error(Box::new(error))
    }
}

pub fn try_get<T>(slice: &[T], index: usize) -> Result<&T, Error> {
    Ok(slice.get(index)?)
}

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

@scottmcm , ваш прототип try-trait-v2 не справляется с этим примером!

Если мы не хотим, чтобы мой пример ломался, try-trait-v2 понадобится что-то вроде:

#[unstable(feature = "try_trait_v2", issue = "42327")]
impl<T, U, E: From<NoneError>> ops::Bubble<Result<U, E>> for Option<T> {
    fn bubble(self) -> ops::ControlFlow<T, Result<U, E>> {
        match self {
            Some(x) => ops::ControlFlow::Continue(x),
            None => ops::ControlFlow::Break(Err(E::from(NoneError))),
        }
    }
}

Каков текущий статус этой функции?

PR # 62606 для документирования реализации try_fold для итераторов должен быть открыт повторно, как только это станет стабильным.

Изменить: обновлена ​​операция с элементом отслеживания для этого ~ scottmcm

Есть ли планы заменить черту Try на любую из альтернатив, предложенных в этой ветке? Версия, предложенная @scottmcm, кажется хорошей. Я хочу продолжать использовать оператор ? с Option , и я думаю, что признак Try следует изменить, чтобы не принудительно использовать семантику Result для Option .

Использование альтернативы @scottmcm позволит нам использовать ? с Option и избавиться от NoneError . Я согласен с @nikomatsakis ( комментарий ), что черта Try не должна способствовать необходимости «искусственно определять тип, представляющий« отказ » Option ».

pub struct Error(Box<dyn std::fmt::Debug>);
impl<T: std::fmt::Debug + 'static> From<T> for Error { fn from(error : T) -> Self { Error(Box::new(error)) } }
type Result<T> = std::result::Result<T, Error>;

Новичок здесь, я был немного упрям ​​в желании получить? чтобы автоматически ввести стирание всех ошибок и опций.
Потратив слишком много времени на то, чтобы понять, почему другие вероятные решения не могут быть реализованы, я обнаружил, что @cuviper - самое близкое к тому, что я могу получить.
Некоторые объяснения помогли бы, но, по крайней мере, я должен был близко познакомиться с некоторыми ограничениями метапрограммирования Rust.
Поэтому я попытался понять это и объяснить конкретными словами.
Эта ветка кажется наиболее вероятным перекрестком, где я, надеюсь, смогу помочь любому, как я, наткнувшемуся на это, не стесняйтесь исправлять:

  • Использование Debug (общего для StdError и NoneError) вместо StdError:
    Общий From<T: StdError> for Error и специальный From<NoneError> for Error конфликтуют
    Потому что было бы неоднозначно, если бы NoneError реализовал StdError (очевидно, даже по умолчанию не разрешает переопределения и обеспечивает исключительность)
    Это можно решить с помощью «отрицательной границы», которая не поддерживается, может быть, потому, что это будет единственный вариант использования? (сверх специализации)
    impl StdError для NoneError может быть определен только вместе с StdError или самим NoneError, так что он будет согласован для любого нисходящего потока.
  • Ошибка не может быть псевдонимом:
    type Error = Box<Debug> связывает Debug, что приводит к конфликту From<T:Debug> for Error с From<T> for T (рефлексивно From для идемпотентности)

Итак, поскольку Error не может реализовать Debug, вам может понадобиться помощник для развертывания в Result с транзитивной Debug:

fn from<T>(result : Result<T>) -> std::result::Result<T, Box<dyn std::fmt::Debug>> { match result { Ok(t) => Ok(t), Err(e) => Err(e.0) } }

Это не может быть ни impl From<Result<T>> for StdResult<T> ни Into<StdResult> for Result<T> (не может подразумевать свойство восходящего потока для восходящего типа)

Например, вы можете использовать его, чтобы получить возврат отладки для завершения:

fn main() -> std::result::Result<(), Box<dyn std::fmt::Debug>> { from(run()) }

Злая идея: возможно, оператор ? каким-то образом может представлять монадическое связывание, поэтому

let x: i32 = something?;
rest of function body

становится

Monad::bind(something, fn(x) {
    rest of function body
})

Ужасная идея, но внутреннему выродку она нравится во мне.

@derekdreery Я не думаю, что это будет хорошо работать с потоком императивного управления, например return и continue

Имейте в виду, что семантика оператора ? уже стабильна. Только фактическая черта Try является нестабильной, и любые изменения должны сохранять тот же стабильный эффект для Option и Result .

@KrishnaSannasi

@derekdreery Я не думаю, что это будет хорошо работать с императивным потоком управления, таким как возврат и продолжение

Я согласен с этим утверждением.

@cuviper

Имейте в виду, что семантика оператора? Оператор уже стабилен. Нестабильным является только фактический признак Try, и любые изменения должны сохранять тот же стабильный эффект для Option и Result.

Верно ли это и в разные эпохи?

В более общем плане, я не думаю, что возможно объединить такие концепции, как .await , ?/ Early return, Option :: map, Result :: map, Iterator :: map в ржавчине, но понимание того, что все они имеют общую структуру, определенно помогает мне стать лучшим программистом.

Извиняюсь за то, что был ОТ.

Я не уверен, что эпохе / редакции будет разрешено изменять ? desugaring, но это, безусловно, усложнит множество перекрестных проблем, таких как макросы.

Моя интерпретация RFC о гарантиях стабильности и эпохах заключается в том, что поведение ? (сахарное или иное) может быть изменено, но его текущее поведение для типов Option / Result должно оставаться неизменным. то же самое, потому что взлом, который вызовет гораздо больший отток, чем мы когда-либо могли надеяться оправдать.

Что, если Option каким-то образом был псевдонимом типа для Result ? Т.е. type Option<T> = Result<T, NoValue> , Option::<T>::Some(x) = Result::<T, NoValue>::Ok(x) , Option::<T>::None = Result::<T, NoValue>::Err(NoValue) ? Для реализации этого потребуется некоторая магия, и это нереально в текущей языковой среде, но, возможно, это стоит изучить.

Мы не можем внести это изменение, потому что есть подразумеваемые признаки, которые полагаются на то, что Option и Result являются отдельными типами.

Если мы проигнорируем это, мы могли бы пойти еще дальше и даже сделать bool псевдонимом для Result<True, False> , где True и False - это типы единиц.

Рассматривалось ли добавление Try impl для единицы () ? Для функций, которые ничего не возвращают, ранний возврат может быть полезен в случае ошибки в некритической функции, такой как обратный вызов журналирования. Или модуль был исключен, потому что лучше никогда не игнорировать ошибки молча в любой ситуации?

Или модуль был исключен, потому что лучше никогда не игнорировать ошибки молча в любой ситуации?

Этот. Если вы хотите игнорировать ошибки в некритическом контексте, вы должны использовать unwrap или один из его вариантов.

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

вы должны использовать развёртку или один из её вариантов.

unwrap() вызывает панику, поэтому я бы не рекомендовал использовать его в некритических контекстах. Это было бы неуместно в ситуациях, когда требуется простой возврат. Можно привести аргумент, что ? не является полностью "тихим" из-за явного, видимого использования ? в исходном коде. Таким образом, ? и unwrap() аналогичны для единичных функций, только с другой семантикой возврата. Единственное различие, которое я вижу, заключается в том, что unwrap() будет иметь видимые побочные эффекты (прервать программу / распечатать трассировку стека), а ? нет.

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

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

Как это будет работать? Просто всегда оценивать как ОК (()) и игнорировать?

Кроме того, не могли бы вы привести пример того, где вы бы хотели это использовать?

Да, идея заключалась в том, что что-то вроде MyCollection :: push может, в зависимости от конфигурации времени компиляции, иметь либо возвращаемое значение Result <(), AllocError>, либо возвращаемое значение (), если коллекция настроена так, чтобы просто паниковать при ошибке. Промежуточному коду, использующему коллекцию, не нужно думать об этом, поэтому, если бы он мог просто _всегда_ использовать ? даже если тип возврата () это было бы удобно.

Спустя почти 3 года, приблизился ли этот вопрос к решению?

@Lokathor, который будет работать только в том случае, если тип возврата Result<Result<X,Y>,Z> или аналогичный невозможен. Но это. Таким образом, невозможно.

Я не понимаю, почему многослойный результат вызывает проблемы. Не могли бы вы уточнить?

Для целей перекрестных ссылок @dureuill предложил альтернативную формулировку внутренних

https://internals.rust-lang.org/t/a-slightly-more-general-easier-to-implement-alternative-to-the-try-trait/12034

@Lokathor
Хорошо, я подумал об этом более глубоко и думаю, что, возможно, нашел довольно хорошее объяснение.

Использование аннотации

Проблема в том, что интерпретация возвращаемого типа или странные аннотации загромождают код. Это было бы возможно, но это затруднило бы чтение кода. (Предварительное условие: #[debug_result] действительно применяет желаемое поведение и изменяет функцию для паники в режиме выпуска вместо возврата Result::Err(...) и разворачивает Result::Ok , но эта часть сложна)

#[debug_result]
fn f() -> Result<X, Y>;

#[debug_result]
fn f2() -> Result<Result<A, B>, Y>;

#[debug_result]
fn g() -> Result<X, Y> {
    // #[debug_result_spoiled]
    let w = f();
    // w could have type X or `Result<X,Y>` based on a #[cfg(debug_...)]
    // the following line would currently only work half of the time
    // we would modify the behavoir of `?` to a no-op if #[cfg(not(debug_...))]
    // and `w` was marked as `#[debug_result]`-spoiled
    let x = w?;

    // but it gets worse; what's with the following:
    let y = f2();
    let z = y?;
    // it has completely different sematics based on a #[cfg(debug_...)],
    // but would (currently?) print no warnings or errors at all,
    // and the type of z would be differently.

    Ok(z)
}

Таким образом, это затруднило бы чтение кода.
Мы не можем просто изменить поведение ? на бездействие,
особенно, если возвращаемое значение #[debug_result] сохраняется во временной переменной и позже "пробует распространяться" с помощью оператора ? . Это сделало бы семантику ? настоящему запутанной, потому что она будет зависеть от многих «переменных», которые не обязательно известны во «время написания» или их трудно угадать, просто прочитав исходный код. #[debug_result] нужно будет испортить переменные, которым присвоены значения функций, но это не сработает, если одна функция помечена #[debug_result] а другая нет, например, следующее будет ошибкой типа.

// f3 is not annotated
fn f3() -> Result<X, Y>;

// later inside of a function:
   // z2 needs to be both marked spoiled and non-spoiled -> type error
   let z2 = if a() {
       f3()
   } else {
       f()
   };
   // althrough the following would be possible:
   let z2_ = if a() {
       f3()?
   } else {
       f()?
   };

Альтернатива: использование нового типа

«Более чистым» решением может быть тип DebugResult<T, E> который просто паникует, когда установлен определенный флаг компиляции и создается из-за ошибки, но в противном случае был бы эквивалентен Result<T, E> противном случае. Но я думаю, что это было бы возможно и с нынешним предложением.

Ответ на последний пост @zserik : Макрос, который вы описали, бесполезен - тип возврата функции, основанный на конфигурации сборки, нарушит каждое совпадение результата функции во всех возможных конфигурациях сборки, кроме единственной, независимо от того, как это было сделано.

@ tema3210 Я знаю. Я только хотел отметить, что идея @Lokathor в целом не сработает на практике. Единственное, что может частично сработать, - это следующее, но только с множеством ограничений, которые, я думаю, не стоит того в долгосрочной перспективе:

// the result of the fn *must* have Try<Ok=()>
// btw. the macro could be already implemented with a proc_macro today.
#[debug_result]
fn x() -> Result<(), XError> {
  /* ..{xbody}.. */
}

// e.g. evaluates roughly to:
//..begin eval
fn x_inner() -> Result<(), XError> {
  /* ..{xbody}.. */
}

#[cfg(panic_on_err)]
fn x() {
  let _: () = x_inner().unwrap();
}
#[cfg(not(panic_on_err))]
fn x() -> Result<(), XError> {
  x_innner()
}

//..end eval

fn y() -> Result<(), YError> {
  /* ... */
  // #[debug_result] results can't be matched on and can't be assigned to a
  // variable, they only can be used together with `?`, which would create
  // an asymetry in the type system, but could otherwise work, althrough
  // usage would be extremely restricted.
  x()?;
  // e.g. the following would fail to compile because capturing the result
  // is not allowed
  if let Err(z) = x() {
    // ...
  }
}

@zserik Возможно ли, что он действительно принимает такую ​​форму?

#[cfg(panic_on_err)]
fn x() -> Result<(), !> {
  let _: () = x_inner().unwrap();
}

#[cfg(not(panic_on_err))]
fn x() -> Result<(), XError> {
  x_innner()
}

хорошо, хорошая идея.

Я не совсем уверен, нужно ли это учитывать до стабилизации, но я заинтересован в реализации трассировки возврата ошибок, и я думаю, что это связано с изменениями в трейте Try или, по крайней мере, в его предоставленном impl за Result чтобы заставить его работать. В конце концов я планирую превратить это в RFC, но я хочу убедиться, что типаж try не стабилизирован таким образом, чтобы его невозможно было добавить позже, если мне потребуется время, чтобы написать упомянутый RFC. Основная идея такова.

У вас есть черта, которую вы используете для передачи информации отслеживания, в которой используется специализация и имплицит по умолчанию для T, чтобы поддерживать обратную совместимость.

pub trait Track {
    fn track(&mut self, location: &'static Location<'static>);
}

default impl<T> Track for T {
    fn track(&mut self, _: &'static Location<'static>) {}
}

А затем вы изменяете реализацию Try для Result чтобы использовать track_caller и передавать эту информацию во внутренний тип,

    #[track_caller]
    fn from_error(mut v: Self::Error) -> Self {
        v.track(Location::caller());
        Self::Err(v)
    }

А затем для типов, для которых вы хотите собирать обратные следы, реализуйте Track

#[derive(Debug, Default)]
pub struct ReturnTrace {
    frames: Vec<&'static Location<'static>>,
}

impl Track for ReturnTrace {
    fn track(&mut self, location: &'static Location<'static>) {
        self.frames.push(location);
    }
}

Использование в конечном итоге выглядит так

#![feature(try_blocks)]
use error_return_trace::{MyResult, ReturnTrace};

fn main() {
    let trace = match one() {
        MyResult::Ok(()) => unreachable!(),
        MyResult::Err(trace) => trace,
    };

    println!("{:?}", trace);
}

fn one() -> MyResult<(), ReturnTrace> {
    try { two()? }
}

fn two() -> MyResult<(), ReturnTrace> {
    MyResult::Err(ReturnTrace::default())?
}

И результат очень дерьмовой версии обратной трассировки выглядит так

ReturnTrace { frames: [Location { file: "examples/usage.rs", line: 18, col: 42 }, Location { file: "examples/usage.rs", line: 14, col: 16 }] }

И вот доказательство его концепции в действии https://github.com/yaahc/error-return-traces

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

trait Try{
     type Error;
     type Ok;
     fn into_result(self)->Result<Self::Ok,Self::Error>;
     fn from_ok(val: Self::Ok)->Self;
     fn from_error<T>(val: T)->Self;
}

Обратите внимание, что здесь компилятор может мономорфизировать from_error , избегая вызова From::from , и можно вручную предоставить метод impl для разных типов ошибок, что приводит к способности оператора ? "разворачивать" разные типы ошибок.

 fn from_error<T>(val: T)->Self;

Как написано, разработчик должен без ограничений принять T любого_ размера. Если вы хотели разрешить это настраиваемое ограничение, это должен быть параметр признака, например Try<T> . Это похоже на комбинацию TryBlock / Bubble<Other> которая была у @scottmcm в https://github.com/rust-lang/rust/issues/42327#issuecomment-457353299 .

Как написано, разработчик должен без ограничений принять T любого_ размера. Если вы хотели разрешить это настраиваемое ограничение, это должен быть параметр признака, например Try<T> . Это похоже на комбинацию TryBlock / Bubble<Other> которая была у @scottmcm в # 42327 (комментарий) .

Я имел в виду, что использование должно выглядеть так:

trait Try{
     //same from above
}
struct Dummy {
    a: u8,
}
struct Err1();
struct Err2();
impl Try for Dummy {
    type Ok=();
    type Error=();

    fn into_result(self)->Result<Self::Ok,Self::Error>{
        std::result::Result::Ok(())
    }
    fn from_ok(val: Self::Ok)->Self{
        Self{a: 0u8}
    }
    fn from_error<T>(val: Err1)->Self where T == Err1{
        Self{a: 1u8}
    }
    fn from_error<T>(val: Err2)->Self where T == Err2{
        Self{a: 2u8}
    }
}

вам нужно разделить Try и TryFromError. Мне это нравится больше, чем первоначальное предложение, но я думаю, что для этого потребуется новый RFC.

(и я все еще думаю, что его следовало назвать «распространять», а не «пытаться», но я отвлекся)

@ tema3210 Думаю, я понимаю ваши намерения, но это не верный Rust.

@ SoniEx2

вам нужно разделить Try и TryFromError.

Верно, поэтому я упомянул дизайн TryBlock / Bubble<Other> . Мы можем обсудить этот выбор наименования, но идея заключалась в том, что раннее возвращение не всегда связано с ошибками как таковыми. Например, многие внутренние методы Iterator используют пользовательский тип LoopState . Для чего-то вроде find поиск того, что вы ищете, не является ошибкой, но мы хотим остановить итерацию на этом этапе.

https://github.com/rust-lang/rust/blob/3360cc3a0ea33c84d0b0b1163107b1c1acbf2a69/src/libcore/iter/mod.rs#L375 -L378

Мы можем обсудить этот выбор наименования, но идея заключалась в том, что раннее возвращение не всегда связано с ошибками как таковыми.

именно поэтому мне не нравится имя «попытка» и я предпочел бы имя «распространять», потому что оно «распространяет» «ранний» возврат: p

(не уверен, имеет ли это смысл? В прошлый раз, когда я поднял эту тему, «распространение» имело смысл только для меня по какой-то причине, и я никогда не мог объяснить это другим.)

Поможет ли эта черта при попытке перезаписать поведение по умолчанию ? fe, чтобы добавить перехватчик журнала fe для регистрации отладочной информации (например, номера файла / строки)?

В настоящее время поддерживается перезапись макросов stdlib, но кажется, что оператор ? не преобразуется явно в макрос try! . Это прискорбно.

@stevenroose Чтобы добавить поддержку этого только в Try , потребуется модификация трейта Try чтобы включить информацию о местоположении файла о том месте, где "произошло" ? .

@stevenroose Чтобы добавить поддержку этого только в Try , потребуется модификация трейта Try чтобы включить информацию о местоположении файла о том месте, где "произошло" ? .

Это неправда, # [track_caller] можно использовать с имплицитными признаками, даже если определение признака не включает аннотацию.

@stevenroose, чтобы ответить на ваш вопрос, да, хотя, если вы хотите распечатать все ? местоположения, через которые распространяется ошибка, вы должны проверить комментарий трассировки возврата ошибки сверху

https://github.com/rust-lang/rust/issues/42327#issuecomment -619218371

Я не уверен, что кто-то уже упоминал об этом, может быть, impl Try for bool , может быть, Try<Ok=(), Error=FalseError> ?
Чтобы мы могли сделать что-то подобное

fn check_key(v: Vec<A>, key: usize) -> bool {
  let x = v.get_mut(key)?; // Option
  x.is_valid()?; // bool
  x.transform()?; // Result
  true
}

Теперь мне приходится использовать возвращаемый тип Option<()> в большинстве случаев, когда я думаю, что ? может сделать код более понятным.

Я не уверен, что кто-то уже упоминал об этом, может быть, impl Try for bool , может быть, Try<Ok=(), Error=FalseError> ?

Это заставит Try вести себя как оператор && на bool s. Как уже отмечалось выше, существуют также варианты использования короткого замыкания в случае успеха, и в этом случае Try должно вести себя как || . Поскольку операторы && и || не намного длиннее для ввода, я также не вижу особых преимуществ в этой реализации.

@calebsander спасибо за любезный ответ.
Это верно для некоторых простых случаев, но я не думаю, что это часто бывает, особенно у нас никогда не могло бы быть статуса присваивания, такого как let x = v.get_mut(key)? в выражении.
Если бы && и || всегда работали, мы могли бы поиграть с .unwrap_or_else() , .and_then() для Option и Error кейсов.
Не могли бы вы выразить текущий код в && и || ?

fn check_key(v: Vec<A>, key: usize) -> bool {
  let x = v.get_mut(key)?; // Option
  x.not_valid().not()?; // bool
  for i in x.iter() {
    if i == 1 { return true }
    if i == 2 { return false }
    debug!("get {}" i);
  }
  let y = x.transform()?; // Result
  y == 1
}

Для некоторых условий, при которых true означает сбой, а false означает успех, !expr? может сбивать с толку, но мы могли бы использовать expr.not()? для выполнения трюка (примечание: для ops::Try , у нас всегда false на Error )

Это верно для некоторых простых случаев, но я не думаю, что это часто бывает, особенно у нас никогда не могло бы быть статуса присваивания, такого как let x = v.get_mut(key)? в выражении.

Что ж, если я неправильно понимаю ваше предложение, простая реализация Try<Ok = (), Error = FalseError> для bool не позволит этого. Вам также потребуется impl From<NoneError> for FalseError чтобы оператор ? мог преобразовать None в false . (И аналогично, если вы хотите применить ? к Result<T, E> внутри функции, которая возвращает bool , вам потребуется impl From<E> for FalseError . Я думаю, что такой реализация blanket будет проблематичной.) Вы также можете просто использовать some_option().ok_or(false)? и some_result().map_err(|_| false)? если вас устраивает возвращаемое значение Result<bool, bool> (которое можно свернуть с помощью .unwrap_or_else(|err| err) ).

Оставляя в стороне проблемы преобразования других ошибок Try в bool , реализация Try вы предлагаете для bool представляет собой просто оператор && . Например, это эквивалентные

fn using_try() -> bool {
    some_bool()?;
    some_bool()?;
    some_bool()
}

а также

fn using_and() -> bool {
    some_bool() &&
    some_bool() &&
    some_bool()
}

(По общему признанию, такие потоки управления, как if и loop которые возвращают не () , переводить сложнее.)

Для некоторых условий, при которых true означает сбой, а false означает успех, !expr? может сбивать с толку, но мы могли бы использовать expr.not()? чтобы сделать трюк (примечание: для ops::Try , у нас всегда false на Error )

Мне не ясно, что false должно представлять случай Error . (Например, Iterator.all() потребует короткого замыкания false , а Iterator::any() true вместо этого потребует ? (а также инвертирования возвращаемого значения функции). Но я не думаю, что это приводит к очень удобочитаемому коду. Возможно, имеет смысл иметь отдельные struct s And(bool) и Or(bool) которые реализуют два разных поведения.

А также, если вы хотите подать заявку? к результатувнутри функции, возвращающей bool, вам нужно будет имплантировать Fromдля FalseError. Я думаю, что такая бланкетная реализация будет проблематичной.

Нет, я не хочу impl From<T> for FalseError , может быть, мы могли бы сделать result.ok()?

Мне не ясно, что false должно представлять случай ошибки.

Я думаю, что это как-то естественно, когда у нас есть bool::then которые сопоставляют true с Some .

Простите, если пропустил - почему нет NoneError impl std::error::Error ? Это делает его бесполезным для любых функций, возвращающих Box<dyn Error> или аналогичные.

Простите, если пропустил - почему нет NoneError impl std::error::Error ? Это делает его бесполезным для любых функций, возвращающих Box<dyn Error> или аналогичные.

Здесь я не эксперт, но я вижу значительные проблемы с попыткой выдать полезное сообщение об ошибке «что-то было None а вы ожидали, что это будет Some » (что в основном то, что вы Набираю сюда - какое-то диагностическое сообщение). По моему опыту, всегда имело смысл использовать комбинатор Option::ok_or_else чтобы вместо этого использовать другой тип ошибки, потому что в качестве вызывающего кода у меня в любом случае гораздо лучший контекст.

Я согласен с @ErichDonGubler. Очень неприятно, что нельзя использовать ? с Option , но на это есть веская причина, и, как пользователь языка, я думаю, что правильная обработка ошибок в общих интересах. Выполнение этой дополнительной работы по привязке контекста ошибки к None вместо простого выполнения ? действительно очень раздражает, но заставляет правильно сообщать об ошибках, что того стоит.

Единственный раз, когда я позволю интерпретировать None как ошибку, - это очень грубое прототипирование. Я подумал, что если мы добавим что-то вроде вызова Option::todo_err() , это вернет Ok внутреннего значения, но вызовет панику при None . Это очень противоречит интуиции, пока вы не проанализируете потребности режима «грубого прототипирования» разработки кода. Он очень похож на Ok(my_option.unwrap()) , но не требует упаковки Ok(...) . Кроме того, он явно определяет характер «задачи», тем самым сообщая о намерении удалить этот код из производственной логики, заменив его правильной привязкой ошибки.

но паникует ни на кого

Твердо чувствую, что мы должны просто оставить это unwrap . Если мы добавили todo_err, он должен вернуть фактический тип ошибки, что вызывает вопрос о том, какой тип ошибки возвращать.

Также я подозреваю, что у fn todo_err(self) -> Result<Self, !> будет очевидная проблема, связанная с требованием ! для стабилизации, что, ну, когда-нибудь.

Мой вариант использования - это действительно грубое прототипирование, когда меня не слишком заботят ошибки. Разве весь NoneError страдает от перечисленных вами проблем? Если решено, что он должен существовать (что, по моему мнению, хорошо, по крайней мере, для прототипирования), я считаю, что он должен подразумевать Error поскольку он назван так.

Из-за того, что у этого типа нет этого impl, я прибегнул к тому, чтобы просто нанести на него .ok_or("error msg") , что тоже работает, но менее удобно.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги