Rust: Проблема отслеживания для блоков оператора `?` И `try` (RFC 243, функции` question_mark` и `try_blocks`)

Созданный на 5 февр. 2016  ·  340Комментарии  ·  Источник: rust-lang/rust

Проблема с отслеживанием для rust-lang / rfcs # 243 и rust-lang / rfcs # 1859.

Проблемы реализации:

  • [x] ? оператор, примерно эквивалентный try! - # 31954
  • [x] try { ... } выражение - https://github.com/rust-lang/rust/issues/39849

    • [x] разрешить синтаксический вопрос do catch { ... }


    • [x] определяет, должны ли блоки catch "оборачиваться" значение результата (сначала адресуется в https://github.com/rust-lang/rust/issues/41414, теперь устанавливается заново в https://github.com/rust- lang / rust / issues / 70941)

    • [] Устранение проблем с выводом типа ( try { expr? }? настоящее время где-то требует явной аннотации типа).

  • [x] согласовать дизайн трейта Try (https://github.com/rust-lang/rfcs/pull/1859)

    • [x] реализовать новый Try (вместо Carrier ) и преобразовать ? для его использования (https://github.com/rust-lang/rust/pull / 42275)

    • [x] добавить impls для Option и так далее, а также подходящее семейство тестов (https://github.com/rust-lang/rust/pull/42526)

    • [x] улучшить сообщения об ошибках, как описано в RFC (https://github.com/rust-lang/rust/issues/35946)

  • [x] зарезервировать try в новой редакции
  • [x] блокировать try{}catch (или другие следующие иденты), чтобы оставить пространство для дизайна открытым для будущего, и указывать людям, как делать то, что они хотят, с помощью match вместо этого
A-error-handling B-RFC-approved B-RFC-implemented B-unstable C-tracking-issue F-try_blocks Libs-Tracked T-lang T-libs

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

@ mark-im Я не думаю, что мы можем разумно переходить от одного к другому после стабилизации. Как бы плохо я ни считал Ok-wrapping, непоследовательная Ok-упаковка, которая пытается угадать, хотите вы этого или нет, была бы еще хуже.

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

В сопутствующем RFC обсуждается обессахаривание, основанное на помеченном возврате / перерыве, получаем ли мы это тоже, или будет только специальная обработка для ? и catch в компиляторе?

РЕДАКТИРОВАТЬ: Я думаю, что помеченный возврат / перерыв - отличная идея отдельно от ? и catch , поэтому, если ответ отрицательный, я, вероятно, открою для него отдельный RFC.

Маркировка возврата / перерыва предназначена исключительно для пояснительных целей.

Пятница, 5 февраля 2016 г., в 15:56, Джонатан Рим [email protected]
написал:

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

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/rust-lang/rust/issues/31436#issuecomment -180551605.

Еще один нерешенный вопрос, который мы должны решить перед стабилизацией, - каким должен быть контракт, которому должны подчиняться impl s из Into - или является ли Into даже правильным признаком для исправления ошибок здесь. (Возможно, это должен быть еще один пункт контрольного списка?)

@reem

Я думаю, что пометка return / break - отличная идея ... Я, наверное, открою для нее отдельный RFC.

Пожалуйста, сделай!

Что касается черты Carrier , вот краткий пример такой черты, которую я написал в начале процесса RFC.
https://gist.github.com/thepowersgang/f0de63db1746114266d3

Как это лечится при парсинге?

struct catch {
    a: u8
}

fn main() {

    let b = 10;
    catch { a: b } // struct literal or catch expression with type ascription inside?

}

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

Также

let c1 = catch { a: 10 };
let c2 = catch { ..c1 }; // <--

struct catch {}
let c3 = catch {}; // <--

+ https://github.com/rust-lang/rfcs/issues/306, если (когда!) реализовано.
Похоже, кроме структурных литералов конфликтов нет.

Учитывая приведенные выше примеры, я за простейшее решение (как обычно) - всегда рассматривайте catch { в позициях выражения как начало блока catch . Все равно никто не называет свои структуры catch .

Было бы проще, если бы вместо catch использовалось ключевое слово.

Это список ключевых слов: http://doc.rust-lang.org/nightly/grammar.html#keywords.

@bluss да, я признаю, что никто из них не велик ... override кажется единственным, что близко. Или мы могли бы использовать do , хе. Или комбинацию, хотя сразу не вижу хороших. do catch ?

do - единственный, который кажется близким ИМО. Суп с ключевыми словами с префиксом do немного нестандартен, не похож ни на одну другую часть языка? while let - это тоже суп из ключевых слов? Теперь это нормально, когда ты к этому привык.

порт try! для использования ?

Нельзя ли перенести ? на использование try! вместо этого? Это позволит использовать случай, когда вы хотите получить путь возврата Result , например, при отладке. С try! это довольно просто, вы просто переопределите макрос в начале файла (или в lib / main.rs):

macro_rules! try {
    ($expr:expr) => (match $expr {
        Result::Ok(val) => val,
        Result::Err(err) => {
            panic!("Error occured: {:?}", err)
        }
    })
}

Вы получите трассировку стека паники, начиная с первого появления try! в пути возврата Result . Фактически, если вы сделаете try!(Err(sth)) если вы обнаружите ошибку вместо return Err(sth) , вы даже получите полную трассировку стека.

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

Было бы здорово, если бы переопределение try! повлияло бы и на оператор ? .

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

Если это предложение требует RFC, дайте мне знать.

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

Трассировка стека - лишь один из примеров (хотя мне кажется, очень полезный).
Если черта Carrier будет работать, может быть, она сможет покрыть такие расширения?

В воскресенье, 7 февраля 2016 г., в 16:14, Рассел Джонстон [email protected]
написал:

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

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/rust-lang/rust/issues/31436#issuecomment -181118499.

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

Рассмотрим обычный случай, когда есть код, возвращающий некоторое значение Result<V,E> . Теперь нам нужно разрешить сосуществование нескольких реализаций трейта Carrier . Чтобы не столкнуться с E0119 , необходимо сделать все реализации вне области видимости (возможно, с помощью различных ? пользователь должен импортировать желаемое. реализация.

Это потребует от всех, даже тех, кто не хочет отлаживать, импортировать желаемую реализацию трейта при использовании ? , для предопределенного значения по умолчанию не будет варианта.

Возможно, E0117 также может быть проблемой, если вы хотите сделать пользовательские реализации Carrier для Result<V,E> , где все типы находятся за пределами границ, поэтому libstd уже должен предоставить набор реализаций Carrier trait с наиболее часто используемыми вариантами использования (тривиальная реализация и реализация panic! ing, возможно, больше).

Возможность переопределения с помощью макроса обеспечит большую гибкость без дополнительной нагрузки на оригинального разработчика (им не нужно импортировать желаемую реализацию). Но я также вижу, что у Rust никогда раньше не было оператора на основе макроса, и что реализация ? помощью макроса невозможна, если catch { ... } должен работать, по крайней мере, без дополнительных языковых элементов ( return_to_catch , throw , помечено break с параметром, используемым в RFC 243).

Я согласен с любой настройкой, которая позволяет получить Result stacktraces с обратным путем Err , при этом нужно только изменить очень небольшой объем кода, предпочтительно в верхней части файла. Решение также должно работать независимо от того, как и где реализован тип Err .

Просто чтобы присоединиться к байкшеддингу: catch in { ... } течет довольно красиво.

catch! { ... } - еще один вариант обратной совместимости.

Кроме того, не то, чтобы я ожидал, что это что-то изменит, но примечание о том, что это сломает макросы с несколькими руками, которые принимали $i:ident ? , точно так же, как описание типа сломало $i:ident : $t:ty .

Не переусердствуйте с обратной совместимостью, просто рассматривайте catch как ключевое слово, за которым следует { (возможно, только в позиции выражения, но я не уверен, сильно ли это изменится с точки зрения совместимости).

Я также могу представить себе некоторые возможные проблемы, которые не связаны со структурными литералами (например, let catch = true; if catch {} ); но я предпочитаю незначительные критические изменения более уродливому синтаксису.

В любом случае, у нас не было средства для добавления новых ключевых слов? Мы могли бы предложить своего рода подписку from __future__ для нового синтаксиса; или укажите номер версии языка ржавчины в командной строке / в Cargo.toml.
Я очень сомневаюсь, что в долгосрочной перспективе мы сможем работать только с теми ключевыми словами, которые уже зарезервированы. Мы не хотим, чтобы каждое из наших ключевых слов имело три разных значения в зависимости от контекста.

Я согласен. Это даже не первый RFC, в котором это появилось (https://github.com/rust-lang/rfcs/pull/1444 - другой пример). Я думаю, это будет не последний. (Также default из https://github.com/rust-lang/rfcs/pull/1210, хотя это не RFC, который я поддерживаю.) Я думаю, нам нужно найти способ добавить честные ключевые слова вместо того, чтобы пытаться выяснить, как специально взломать грамматику для каждого нового случая.

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

@japaric Вы заинтересованы в том, чтобы возродить свой старый пиар и заняться этим?

@aturon Моя реализация просто обессахаривала foo? же, как try!(foo) . Он также работал только с вызовами методов и функций, т.е. foo.bar()? и baz()? работают, а quux? и (quux)? - нет. Подойдет ли это для начальной реализации?

@japaric По какой причине он ограничился методами и вызовами функций? Разве не было бы проще разобрать его как обычный постфиксный оператор?

Что послужило причиной ограничения использования методов и вызовов функций?

самый простой способ (для меня) проверить расширение ?

Разве не было бы проще разобрать его как обычный постфиксный оператор?

вероятно

@japaric Вероятно, было бы хорошо обобщить его до полного постфиксного оператора (как предлагает @eddyb ), но нормально получить ? с простым обессахариванием, а затем добавить catch позже.

@aturon Хорошо, я посмотрю версию постфикса на следующей неделе, если меня никто не

перебазировал / обновил мой PR в # 31954 :-)

А как насчет поддержки трассировки стека? Это запланировано?

Я ненавижу быть парнем +1, но трассировки стека сэкономили мне много времени в прошлом. Может быть, на отладочных сборках и при попадании в путь ошибки? оператор мог бы добавить файл / строку к Vec в Результат? Может, Vec тоже можно отладить?

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

Я продолжаю сталкиваться с ситуацией, когда я хочу использовать try! / ? внутри итераторов, возвращающих Option<Result<T, E>> . К сожалению, в настоящее время это не работает. Интересно, можно ли перегрузить черту носителя, чтобы поддержать это, или вместо этого он перейдет в более общий From ?

@hexsel Я действительно хочу, чтобы тип Result<> содержал vec указателей инструкций в отладке и? добавил бы к нему. Таким образом, информация DWARF может быть использована для построения читаемой трассировки стека.

@mituhiko Но как вы могли создать и сопоставить с шаблоном Result ? Это _just_ enum банкомат.

Что касается упаковки Option , я считаю, что вы хотите Some(catch {...}) .

В настоящее время моя привычка - делать try!(Err(bla)) вместо return Err() , так что я могу позже переопределить макрос try с тем, который вызывает панику, чтобы получить обратную трассировку. У меня это работает хорошо, но код, с которым я имею дело, очень низкого уровня и в основном является источником ошибок. Мне все равно придется избегать ? если я использую внешний код, который возвращает Result .

@eddyb потребуется языковая поддержка для переноса скрытых значений в дополнение, которыми вам нужно манипулировать другими способами. Мне было интересно, можно ли это сделать другими способами, но я не понимаю, как это сделать. Единственный другой способ - это стандартизированное окно ошибок, в котором могут быть дополнительные данные, но нет необходимости иметь упакованные ошибки, и большинство людей этого не делают.

@mituhiko Я могу придумать новый (по умолчанию) метод для трейта Error и TLS.
Последний иногда используют дезинфицирующие средства.

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

@mituhiko TLS? Не совсем, просто нужно иметь возможность сравнивать ошибку по значению.

Или даже просто по типу (со связыванием входов и выходов From ), сколько одновременных ошибок, из которых вы хотите, чтобы трассировки стека должны были распространяться одновременно?

Я лично против добавления Result -специфичных хаков для компилятора, когда работают более простые решения.

@eddyb ошибка проходит вверх по стеку. Вам нужен EIP на каждом уровне стека, а не только там, где он возникает. Также ошибки: а) в настоящее время несопоставимы; б) то, что они сравниваются, не означает, что это одна и та же ошибка.

сколько одновременных ошибок вы хотите, чтобы трассировки стека когда-либо должны были распространяться одновременно

Любая ошибка обнаружена и повторно выдана как другая ошибка.

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

Я не понимаю, как работает более простое решение, но, возможно, мне что-то там не хватает.

Вы можете сохранять указатель инструкции при каждом ? и соотносить его с типом ошибки.
«Любая ошибка обнаружена и повторно выдана как другая ошибка». Но как бы вы сохранили эту информацию, если бы она была скрыта в Result ?

Но как бы вы сохранили эту информацию, если бы она была скрыта в Result?

Вам не нужно сохранять эту информацию в результате. Что вам действительно нужно сохранить, так это уникальный идентификатор источника сбоя, чтобы вы могли его коррелировать. И поскольку признак ошибки - это просто признак и не имеет памяти, вместо этого он может быть сохранен в результате. Сам указатель инструкции vec ни в коем случае не должен храниться в результате, который может перейти в TLS.

Один из способов - вызвать метод failure_id(&self) для результата, и он вернет i64 / uuid или что-то, что идентифицирует источник сбоя.

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

«компилятор вводит инструкцию для записи кадра стека, через который он проваливается» - но ? явно, это не похоже на исключения, или вам не нравится записывать _только_ ? через которые он прошел?

В любом случае, если вы вручную распаковываете ошибку, а затем помещаете ее обратно в Err , как бы сохранить этот идентификатор?

"И поскольку признак ошибки - это просто признак и не имеет памяти, вместо этого он может быть сохранен в результате"
Существует вариант: реализация трейта Error могла бы быть в компиляторе в специальном регистре, чтобы добавить к типу дополнительное целочисленное поле, создание типа инициирует создание идентификатора, а копирование / удаление будет эффективно увеличивать / уменьшать счетчик ссылок (и в конечном итоге очищать его из TLS «набора ошибок в полете», если Result::unwrap не используется).

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

РЕДАКТИРОВАТЬ : На этом этапе вы можете также вставить туда Rc<ErrorTrace> .
EDIT2 : Что я даже говорю, вы можете очистить соответствующую трассировку ошибки на catch .
РЕДАКТИРОВАТЬ3 : На самом деле, при падении, см. Ниже лучшее объяснение.

"компилятор вводит инструкцию для записи кадра стека, через который он проваливается" - но? явно, это не что иное, как исключения, или вам не нравится записывать только? это прошло?

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

В любом случае, если вы вручную распаковываете ошибку, а затем помещаете ее обратно в Err, как этот идентификатор вообще будет сохранен?

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

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

Хорошо, я мог видеть, что возвращение Result напрямую связано с особым случаем в сложных функциях с несколькими путями возврата (некоторые из которых будут ранними возвратами из ? ).

Если это слишком волшебно, это может быть ограничено подмножеством операций.

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

@eddyb Обычный случай ручной обработки ошибок - это IoError, когда вы хотите обработать подмножество:

loop {
  match establish_connection() {
    Ok(conn) => { ... },
    Err(err) => {
      if err.kind() == io::ErrorKind::ConnectionRefused {
        continue;
      } else {
        return Err(err);
      }
    }
  }
}

@mituhiko Тогда сохранение идентификатора внутри io::Error определенно сработает.

Итак, резюмируя, Vec<Option<Trace>> "разреженная целочисленная карта" в TLS, с ключом struct ErrorId(usize) и доступная через 3 элемента языка:

  • fn trace_new(Location) -> ErrorId , при создании значения ошибки, отличного от const
  • fn trace_return(ErrorId, Location) , непосредственно перед возвратом из функции, _объявленной_ как -> Result<...> (т.е. не является универсальной функцией при возврате, которая _happens_ будет использоваться с типом Result там)
  • fn trace_destroy(ErrorId) , при сбросе значения ошибки

Если преобразование выполняется в MIR, Location может быть вычислено из Span инструкции, запускающей либо построение значения ошибки, либо запись в Lvalue::Return , что намного надежнее. чем указатель инструкций IMO (в любом случае это нелегко получить в LLVM, вам нужно будет выдать встроенный asm для каждой конкретной платформы).

@eddyb

Разве это не приведет к раздуванию двоичного кода?

@ arielb1 Вы бы сделали это в режиме отладки только тогда, когда debuginfo все равно раздувает двоичный файл - вы также можете повторно использовать debuginfo с умом _shrug_.

@eddyb, что такое Location в этом случае? Не уверен, что такое чтение IP. Конечно, вам нужен собственный JS для каждой цели, но это не так уж и сложно.

@mituhiko, это может быть та же информация, которую мы используем для проверок переполнения и других паник, core::panicking::panic .

Зачем упаковывать столько информации о стеке в Error / Result когда стек разматывается? Почему бы просто не запустить код, пока стек еще есть? Например, что, если вас интересуют переменные на пути стека? Просто позвольте людям запускать собственный код при вызове Err , например, для вызова отладчика по их выбору. Это то, что try! уже предоставляет, поскольку является (переопределяемым) макросом. Самый простой способ - запаниковать из- Err случая

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

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

  • ? переопределить аналогичным образом, проще всего было бы определить ? как сахар для try! - особенно необходимо, чтобы отловить не возникающие ошибки на границе ящика как изложено в моем комментарии выше.
  • наличие более мощной макросистемы, такой как сопоставление по типу, чтобы обеспечить еще большую гибкость. Да, можно подумать о том, чтобы поместить это в традиционную систему признаков, но ржавчина не допускает переопределения реализаций признаков, так что это будет немного сложно.

@ est31 Стек не _автоматически_ "раскручивается", как в панике, это синтаксический сахар для досрочного возврата.
И да, вы можете заставить компилятор вставлять вызовы некоторых пустых функций с фиксированными именами, на которых вы можете установить точку останова, это довольно просто (а также иметь информацию, которая позволяет вам это делать, например, call dump(data) - где dump и data - аргументы, которые могут видеть отладчики).

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

@hexsel Да, вот почему я предпочитаю метод на основе TLS, где Result::unwrap или какой-нибудь новый метод ignore (или даже всегда при удалении ошибки?) сбрасывает трассировку в stderr.

@eddyb Если вы добавляете указатель инструкции или что-то производное от значения в стек, например структуру данных в TLS, вы в основном перестраиваете свою уменьшенную версию реального стека. Возврат уменьшает стек на одну запись. Итак, если вы сделаете это, возвращая вам _unwind_ часть стека, создавая его ограниченную версию в каком-то другом месте в ОЗУ. Возможно, «раскрутка» - неправильный термин для поведения, возникающего в результате «законных» возвратов, но если весь код выполняет ? или try! и в конце вы перехватываете, конечный результат будет таким же. Замечательно, что ржавчина не приводит к автоматическому распространению ошибок, мне очень понравилось, как java требует, чтобы все исключения указывались после ключевого слова throws , ржавчина - хорошее улучшение в этом отношении.

@hexsel подход, основанный на переопределении (поскольку он уже существует сейчас для try! ), позволит это - вы можете запустить любой код, который хотите, и войти в любую систему ведения журнала. Вам может потребоваться некоторое обнаружение "дубликатов", когда несколько try поймают одну и ту же ошибку, поскольку она распространяется вверх по стеку.

@ est31 Переопределение try! работает только в вашем собственном коде (это буквально затенение при импорте макросов), это должно быть что-то другое, например, наши «слабые элементы языка».

@ est31 Это не совсем правильно (насчет раскрутки), трассировки и реальный стек не обязательно имеют какие-либо отношения, потому что перемещение Result s не обязательно должно идти вверх в исходной трассировке, оно может идти боком тоже.
Кроме того, если двоичный файл оптимизирован, большая часть обратной трассировки пропадает, а если у вас нет отладочной информации, тогда Location строго превосходит. Вы даже можете запустить плагин обфускации, который заменяет всю исходную информацию случайными хэшами, которые могут быть сопоставлены разработчиками какого-либо продукта с закрытым исходным кодом.

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

Об этом - у меня были некоторые мысли о трюке на уровне системы, где {Option,Result}::unwrap будет иметь дополнительный аргумент типа, по умолчанию тип, зависящий от местоположения, из которого была вызвана функция, так что паника от них будет более полезная информация о местоположении.

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

Переопределение try! вообще не является решением, потому что оно содержится в вашем собственном ящике. Это неприемлемо для отладки. Я уже много пробовал, пытаясь справиться с текущим try! . Особенно, если у вас задействовано много ящиков, а ошибки трансмутируются несколько раз на пути через стек, практически невозможно выяснить, где возникает ошибка и почему. Если подобный бандаж является решением, тогда нам придется пересмотреть обработку ошибок в целом для больших проектов Rust.

@eddyb, значит, вы предлагаете буквально встроить имя файла, имя функции, номер строки и номер столбца в этот вектор? Это кажется чрезвычайно расточительным, особенно потому, что эта информация уже содержится в гораздо более удобном для обработки формате в DWARF. Кроме того, DWARF позволяет нам использовать тот же процесс относительно дешево в производстве, тогда как такая информация о местоположении кажется настолько расточительной, что никто никогда не будет запускать с ней бинарный выпуск.

Насколько это будет значительно более расточительным, чем информация DWARF? Имена файлов будут дедуплицированы, и на x64 все это имеет размер 3 указателя.

@mitsuhiko, значит, вы согласны с общим направлением, но не с его техническими особенностями?

Насколько легко предоставить информацию DWARF общему Rust API?

@eddyb, потому что информация DWARF содержится не в двоичном выпуске, а в отдельных файлах. Таким образом, я могу иметь файлы отладки на серверах символов и отправлять пользователям разделенный двоичный файл.

@mituhiko О, это совершенно другой подход, чем я предполагал. Rust в настоящее время не поддерживает этот банкомат, AFAIK, но я согласен, что должен.

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

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

Да, try! содержится в вашем ящике. Если вы передаете указатель на функцию или аналогичный код библиотеки, и в вашей функции есть ошибка, попытка все равно будет работать. Если используемый вами ящик имеет внутреннюю ошибку или ошибку, вам может потребоваться его исходный код для уже отладки, если возвращенная информация Err не помогает. Трассировки стека полезны только в том случае, если у вас есть доступ к коду, поэтому библиотеки с закрытым исходным кодом (чей код вы не можете изменить) так или иначе должны будут пройти поддержку.

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

Модель try реализуема очень легко, просто десахарируйте ? до try , требуются только дополнительные языковые расширения, если входит catch (вы бы добавили это поведение внутри жестко запрограммированного поведения из ? любом случае)

@eddyb "Считаете ли вы, что указатели инструкций

Вот как в целом работает отладка собственных двоичных файлов. Мы (Sentry) на данный момент почти полностью используем это для поддержки iOS. Мы получаем аварийный дамп и преобразуем адреса с llvm-symbolizer в фактические символы.

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

@eddyb да. Только что посмотрел код паники. Было бы неплохо, если бы это действительно был API, который мог бы использовать код Rust. Я вижу, что это полезно еще в нескольких местах.

Об этом - у меня были некоторые мысли о трюке на уровне системы, где {Option, Result} :: unwrap будет иметь дополнительный аргумент типа, по умолчанию тип, зависящий от местоположения, из которого была вызвана функция, так что паника от них будет иметь гораздо более полезную информацию о местоположении.

Говоря о которых...

@glaebhoerl Ха, может быть, тогда действительно стоит заняться этим? Хотя бы как какой-нибудь нестабильный эксперимент.

@eddyb Понятия не имею. Вероятно, стоит сначала обсудить это с некоторыми специалистами GHC, которые занимались этим, я только слышал об этом мимоходом и только что поискал ссылку в Google. А в Rust нет фактических неявных параметров, как у GHC; Могут ли вместо этого работать параметры типа по умолчанию - это интересный вопрос (возможно, вы задумались о нем больше, чем я).

Просто подумайте: было бы полезно иметь переключатель, который заставляет rustc в особом случае создавать Err , так что он вызывает функцию fn(TypeId, *mut ()) с полезной нагрузкой перед возвратом . Этого должно быть достаточно, чтобы начать с основных вещей, таких как фильтрация ошибок на основе полезной нагрузки, захват отладчиком, если он видит интересующую ошибку, или захват обратных трассировок для определенных видов ошибок.

PR 33389 добавляет экспериментальную поддержку признака Carrier . Поскольку он не был частью исходного RFC, он должен пройти особенно тщательный период изучения и обсуждения, прежде чем мы перейдем к FCP (который, вероятно, должен быть отделен от FCP для остальной части оператора ? ). См. Эту ветку обсуждения для более подробной информации.

Я против расширения ? до Option s.

Формулировка RFC довольно ясно говорит о том, что оператор ? предназначен для распространения ошибок / "исключений".
Использование Option для сообщения об ошибке - неправильный инструмент. Возврат None является частью нормального рабочего процесса успешной программы, а возврат Err всегда указывает на ошибку.

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

@tomaka, можем ли мы продолжить обсуждение в ветке обсуждения ? (Я уже резюмировал ваше возражение ) Лично я считаю, что долгие обсуждения GH становятся довольно громоздкими, и было бы также неплохо иметь возможность отделить обсуждение этого конкретного пункта от других будущих моментов или вопросов, которые могут возникнуть.

@eddyb Вот документы выпущенной версии неявной функции GHC вызовов, о которой я упоминал ранее .

В последнее время здесь нет обновлений. Кто-нибудь работает над продвижением этого вперед? Остальные задачи в OP по-прежнему точны? Может ли что-нибудь здесь потребоваться электронная помощь?

В прошлые выходные я поигрался, можно ли написать клиент Sentry для Rust, который имеет хоть какой-то смысл. Поскольку большая часть обработки ошибок теперь фактически основана на результатах, а не на панике, я заметил, что полезность этого сильно ограничена до того момента, когда я просто решил полностью отказаться от этого.

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

К сожалению, в настоящее время я не понимаю, как можно записать IP-адрес без серьезных изменений в работе результатов. Кто-нибудь еще играл с этой идеей раньше?

Мы обсуждали это на встрече @ rust-lang / lang. Некоторые вещи, которые возникли:

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

Например, я сам с некоторой регулярностью пишу такой код:

let v: Vec<_> = try!(foo.iter().map(|x| x.to_result()).collect());

где я полагаюсь на try! чтобы сообщить о выводе типа, что я ожидаю, что collect вернет Result<Vec<_>, _> . Если использовать ? , этот вывод _может_ потерпеть неудачу в будущем.

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

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

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

Причина нежелания взаимного преобразования заключается в том, что это, вероятно, приведет к нечеткой логике, что-то вроде того, как C разрешает if x даже когда x имеет целочисленный тип. То есть, если Option<T> обозначает значение, где None является частью домена этого значения (как и должно), а Result<> представляет (обычно) сбой функции чтобы добиться успеха, тогда предположение, что None означает, что функция должна выйти из строя, кажется подозрительным (и вроде своего рода произвольного соглашения). Но я полагаю, что это обсуждение может подождать до RFC.

Причина нежелания взаимного преобразования заключается в том, что это, вероятно, приведет к нечеткой логике, что-то вроде того, как C разрешает if x даже если x имеет целочисленный тип. То есть, если Option<T> обозначает значение, где None является частью домена этого значения (как и должно), а Result<> представляет (обычно) сбой функции для достижения успеха, тогда предположение, что None означает, что функция должна выйти из строя, кажется подозрительным (и похоже на своего рода произвольное соглашение). Но я полагаю, что это обсуждение может подождать до RFC.

Я полностью согласен с этим.

Еще один вопрос, по которому мы договорились о стабилизации ворот, заключался в том, чтобы закрепить контракты, которым должны подчиняться impl s признака From (или любой другой черты, которую мы используем для Err -upcasting ).

@glaebhoerl

Еще один вопрос, который мы договорились о стабилизации ворот, заключался в том, чтобы закрепить контракты, которые подразумевают черту From, которой следует подчиняться (или любую другую черту, которую мы в итоге используем для повышения ошибок).

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

Кстати, в каком состоянии находятся выражения catch ? Реализованы ли они?

К сожалению нет :(

26 июля 2016 г. в 06:41:44 -0700 Александр Булаев написал:

Кстати, в каком состоянии находятся выражения catch ? Реализованы ли они?


Вы получаете это, потому что вы являетесь автором темы.
Ответьте на это письмо напрямую или просмотрите его на GitHub:
https://github.com/rust-lang/rust/issues/31436#issuecomment -235270663

Может, стоит спланировать внедрение? Нет ничего более удручающего, чем принятые, но не реализованные RFC ...

cc # 35056

FYI, https://github.com/rust-lang/rfcs/pull/1450 (типы для вариантов перечисления) откроет несколько интересных способов реализации Carrier . Например, что-то вроде:

trait Carrier {
    type Success: CarrierSuccess;
    type Error: CarrierError;
}

trait CarrierSuccess {
    type Value;
    fn into_value(self) -> Self::Value;
}

// (could really use some HKT...)
trait CarrierError<Equivalent: CarrierError> {
    fn convert_error(self) -> Equivalent;
}

impl<T, E> Carrier for Result<T, E>
{
    type Success = Result::Ok<T, E>;
    type Error = Result::Err<T, E>;
}

impl<T, E> CarrierSuccess for Result::Ok<T, E> {
    type Value = T;
    fn into_value(self) -> Self::Value {
        self.0
    }
}

impl<T, E1, E2> CarrierError<Result::Err<T, E2>> for Result::Err<T, E1>
    where E2: From<E1>,
{
    fn convert_error(self) -> Result::Err<T, E2> {
        Err(self.into())
    }
}

impl<T> Carrier for Option<T>
{
    type Success = Option::Some<T>;
    type Error = None;
}

impl<T> CarrierSuccess for Option::Some<T> {
    type Value = T;
    fn into_value(self) -> Self::Value {
        self.0
    }
}

impl<T> CarrierError<Option::None> for Option::None {
    fn convert_error(self) -> Option::None {
        self
    }
}

fn main() {
    let value = match might_be_err() {
        ok @ Carrier::Success => ok.into_value(),
        err @ Carrier::Error => return err.convert_error(),
    }
}

Я просто хотел поделиться некоторыми мыслями с https://github.com/rust-lang/rust/pull/35056#issuecomment -240129923. Этот PR вводит черту Carrier с фиктивным типом. Намерение было защитить - в частности, мы хотели стабилизировать ? без необходимости стабилизировать его взаимодействие с выводом типа. Эта комбинация черты и фиктивного типа казалась вполне консервативной.

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

Теперь, говоря немного более умозрительно, я _ предвижу_, что если мы действительно примем черту Carrier , мы захотим запретить взаимное преобразование между носителями (тогда как эта черта в основном является способом преобразования в / из Result ). Так что интуитивно, если вы примените ? к Option , это нормально, если fn вернет Option ; и если вы примените ? к Result<T,E> , это нормально, если fn вернет Result<U,F> где E: Into<F> ; но если вы примените ? к Option и fn вернет Result<..> , это не нормально.

Тем не менее, такое правило трудно выразить в современной системе типов. Наиболее очевидной отправной точкой было бы что-то вроде HKT (которого, конечно, у нас на самом деле нет, но пока давайте проигнорируем это). Однако это явно не идеально. Если бы мы использовали его, можно было бы предположить, что параметр Self для Carrier имеет вид type -> type -> type , поскольку Result может реализовать Carrier . Это позволило бы нам выражать такие вещи, как Self<T,E> -> Self<U,F> . Однако это _не_ не обязательно применимо к Option , который имеет вид type -> type (все это, конечно, будет зависеть именно от того, какую именно систему HKT мы приняли, но я не думаю, что мы » Я перейду к "лямбдам общего типа"). Еще более экстремальным может быть такой тип, как bool (хотя я не хочу реализовывать Carrier для bool, я ожидаю, что некоторые люди могут захотеть реализовать Carrier для нового типа 'd bool).

Я считал, что правила набора для ? сами по себе могут быть особенными: например, мы могли бы сказать, что ? может применяться только к номинальному типу Foo<..> _some_ kind, и что он будет соответствовать признаку Carrier этого типа, но для этого потребуется, чтобы возвращаемый тип включающей fn также был Foo<..> . Таким образом, мы бы в основном создали экземпляр Foo с параметрами нового типа. Обратной стороной этой идеи является то, что, если не известен ни тип, к которому применяется ? , ни тип включающей fn, мы не сможем обеспечить соблюдение этого ограничения без добавления какого-либо нового типа обязательного признака. Это тоже довольно спонтанно. :) Но это сработает.

Еще у меня возникла мысль, что мы могли бы пересмотреть черту Carrier . Идея заключалась бы в том, чтобы иметь Expr: Carrier<Return> где Expr - это тип, к которому применяется ? , а Return - тип среды. Например, возможно, это могло бы выглядеть так:

trait Carrier<Target> {
    type Ok;
    fn is_ok(&self) -> bool; // if true, represents the "ok-like" variant
    fn unwrap_into_ok(self) -> Self::Ok; // may panic if not ok
    fn unwrap_into_error(self) -> Target; // may panic if not error
}

Затем expr? десахаров в:

let val = expr;
if Carrier::is_ok(&val) {
    val.unwrap_into_ok()
} else {
    return val.unwrap_into_error();
}

Ключевое отличие здесь в том, что Target будет не типом _error_, а новым типом Result . Так, например, мы могли бы добавить следующее:

impl<T,U,E,F> Carrier<Result<U,F>> for Result<T,E>
    where E: Into<F>
{
    type Ok = T;
    fn is_ok(&self) -> bool { self.is_ok() }
    fn unwrap_into_ok(self) -> Self::Ok { self.unwrap() }
    fn unwrap_into_error(self) -> { Err(F::from(self.unwrap_err())) }
}

И тогда мы могли бы добавить:

impl<T> Carrier<Option<T>> for Option<T> {
    type Ok = T;
    fn is_ok(&self) -> bool { self.is_some() }
    fn unwrap_into_ok(self) -> Self::Ok { self.unwrap() }
    fn unwrap_into_error(self) -> { debug_assert!(self.is_none()); None }
}

И, наконец, мы можем реализовать для bool вот так:

struct MyBool(bool);
impl<T> Carrier<MyBool> for MyBool {
    type Ok = ();
    fn is_ok(&self) -> bool { self.0 }
    fn unwrap_into_ok(self) -> Self::Ok { debug_assert!(self.0); () }
    fn unwrap_into_error(self) -> { debug_assert!(!self.0); self }
}

Теперь эта версия более гибкая. Например, мы _could_ разрешить преобразование значений Option в Result , добавив impl например:

impl<T> Carrier<Result<T,()>> for Option<T> { ... }

Но, конечно, мы не обязаны (и не стали бы).

@Stebalien

К вашему сведению, rust-lang / rfcs # 1450 (типы для вариантов перечисления) откроет несколько интересных способов реализации Carrier

Когда я писал эту идею, я думал о типах для вариантов перечисления и о том, как это может повлиять на вещи.

Одна вещь, которую я заметил при написании кода, использующего ? - это то, что немного раздражает отсутствие какого-либо ключевого слова throw - в частности, если вы напишете Err(foo)? , компилятор не t _знайте_, что это вернется, поэтому вам нужно написать return Err(foo) . Это нормально, но тогда вы не получите конверсий into() не написав их сами.

Это происходит в следующих случаях:

let value = if something_or_other() { foo } else { return Err(bar) };

О, я должен добавить еще кое-что. Тот факт, что мы позволяем impls влиять на вывод типа _должен_ означать, что foo.iter().map().collect()? , в контексте, где fn возвращает Result<..> , я подозреваю, что аннотации типов не потребуются, поскольку, если мы знаем, что Тип возврата fn - Result , потенциально может применяться только один impl (по крайней мере, локально).

О, и, возможно, чуть лучшая версия моего трейта Carrier :

trait Carrier<Target> {
    type Ok;
    fn into_carrier(self) -> Result<Self::Ok, Target>;
}

где вы бы реализовали это как:

impl<T,U,E,F> Carrier<Result<U,F>> for Result<T,E>
    where E: Into<F>
{
    type Ok = T;
    fn into_carrier(self) -> Result<T, Result<U,F>> {
        match self { Ok(v) => Ok(v), Err(e) => Err(e.into()) }
    }
}

И expr? сгенерирует такой код:

match Carrier::into_carrier(expr) {
    Ok(v) => v,
    Err(e) => return e,
}

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

@nikomatsakis IMO, признак должен быть IntoCarrier а IntoCarrier::into_carrier должен возвращать Carrier (новое перечисление) вместо повторного использования такого результата. То есть:

enum Carrier<C, R> {
    Continue(C),
    Return(R),
}
trait IntoCarrier<Return> {
    type Continue;
    fn into_carrier(self) -> Carrier<Self::Continue, Return>;
}

@Stebalien конечно, кажется в порядке.

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

Я открыл rust-lang / rfcs # 1718, чтобы обсудить черту Carrier.

Слушайте, слышите! Оператор ? теперь конкретно входит в окончательный период комментариев . Это обсуждение длится примерно в течение этого цикла выпуска, который начался 18 августа. Склонен к стабилизации оператора ? .

Что касается признака-носителя , временная версия приземлилась в # 35777, которая должна гарантировать, что у нас есть свобода выбора любого пути, предотвращая нежелательное взаимодействие с выводом типа.

Участники @ rust-lang / lang, пожалуйста, отметьте свое имя, чтобы выразить согласие. Оставьте комментарий с проблемами или возражениями. Остальные, пожалуйста, оставляйте комментарии. Благодаря!

  • [x] @nikomatsakis
  • [x] @nrc
  • [x] @aturon
  • [x] @eddyb
  • [] @pnkfelix (в отпуске)

Интересно, будут ли библиотеки на основе токио часто использовать and_then . Это был бы один из аргументов в пользу того, чтобы foo().?bar() было сокращением для foo().and_then(move |v| v.bar()) , чтобы результаты и фьючерсы могли использовать одну и ту же нотацию.

Чтобы было ясно, этот FCP касается функции question_mark , а не уловки , правильно? Название этого выпуска подразумевает отслеживание обеих этих функций в этом выпуске.

@seanmonstar последний даже не реализован, так что да. Предположительно, если FCP приведет к принятию, это будет изменено для отслеживания catch .

Чтобы было ясно, этот FCP касается функции question_mark, а не уловки, правильно? Название этого выпуска подразумевает отслеживание обеих этих функций в этом выпуске.

Да, только функция question_mark .

В продолжение https://github.com/rust-lang/rfcs/issues/1718#issuecomment -241764523. Я думал, что текущее поведение ? можно обобщить до «привязать текущее продолжение», но часть map_err(From::from) из ? делает его немного больше, чем просто bind: /. Если мы вообще добавим экземпляр From для результата, то я полагаю, что семантика может быть v? => from(v.bind(current-continuation)) .

В этом внутреннем потоке было много дискуссий об относительных достоинствах ? :

https://internals.rust-lang.org/t/the-operator-will-be-harmful-to-rust/3882/

У меня сейчас нет времени делать глубокое резюме. Насколько я помню, комментарии сосредоточены на вопросе, достаточно ли виден ? , чтобы привлечь внимание к ошибкам, но я, вероятно, упускаю из виду другие аспекты обсуждения. Если у кого-то еще есть время подвести итоги, было бы здорово!

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

С try! было очевидно, что где-то может быть возврат, потому что макрос может это сделать. С ? в качестве скрытого return у нас было бы 3 способа вернуть значения из функции:

  • неявный возврат
  • явный возврат
  • ?

Но мне это нравится, как сказал @CryZe :

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

пусть a = попробуйте! (x? .y? .z);

Это помогает сделать код более кратким и не скрывает возврата. И это знакомо по другим языкам, например, coffeescript .

Каким образом разрешение ? на уровне выражения, а не на уровне функции повлияет на фьючерсы? Для всех остальных случаев использования мне это кажется нормальным.

Но мне это нравится, как сказал @CryZe :

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

пусть a = попробуйте! (x? .y? .z);

Я постулировал это. Думаю, это было бы идеальным решением.

С помощью try !, было очевидно, что где-то может быть возврат, потому что макрос может это сделать.

Это очевидно только потому, что вы знакомы с тем, как работают макросы в Rust. Что будет точно так же, как только ? станет стабильным, широко распространенным и объясненным в каждом вступлении к Rust.

@conradkleinespel

у нас было бы 3 способа вернуть значения из функции:

  • неявный возврат

В Rust нет «неявного возврата», в нем есть выражения, которые возвращают значение. Это важное различие.

if foo {
    5
}

7

Если бы в Rust был «неявный возврат», этот код компилировался бы. Но это не так, вам нужно return 5 .

Что будет точно так же однажды? стабильна, широко распространена и объясняется в каждом вступлении к Rust.

Пример того, как это может выглядеть, https://github.com/rust-lang/book/pull/134

С помощью try !, было очевидно, что где-то может быть возврат, потому что макрос может это сделать.
Это очевидно только потому, что вы знакомы с тем, как работают макросы в Rust. Что будет точно так же однажды? стабильна, широко распространена и объясняется в каждом вступлении к Rust.

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

@hauleth

пусть a = попробуйте! (x? .y? .z);

Я постулировал это. Думаю, это было бы идеальным решением.

Я категорически не согласен. Так же вы получите волшебный символ, который работает только в try! а не снаружи.

@hauleth

пусть a = попробуйте! (x? .y? .z);
Я постулировал это. Думаю, это было бы идеальным решением.
Я категорически не согласен. Как вы получите волшебный символ, который работает только при попытке! а не снаружи.

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

@steveklabnik Я думаю о Rust как о языке с неявным возвратом. В вашем примере 5 не был возвращен неявно, но давайте возьмем следующее:

fn test() -> i32 {
    5
}

Здесь неявно возвращается 5 , не так ли? В отличие от return 5; вам понадобятся в вашем примере. Это дает два разных способа вернуть значение. Что меня несколько сбивает с толку в Rust. Добавление третьего не помогло бы ИМО.

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

@steveklabnik Хорошо, спасибо, что

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

@hauleth The? В этом случае оператор будет просто синтаксическим сахаром для and_then. Таким образом, вы можете использовать его в гораздо большем количестве случаев, и не будет сложно пропустить возврат. Это также то, что есть на всех других языках, у которых есть? оператор. Ржавчины? Оператор в текущей реализации будет полной противоположностью тому, что делают все другие языки. Также and_then - это функциональный подход, который в любом случае приветствуется, поскольку он имеет четкий поток управления. Так что просто делаем? синтаксический сахар для and_then, а затем сохранить текущую попытку! для явного «разворачивания и возврата», кажется, намного более чистая ситуация, делая возвраты более заметными и? Оператор более гибкий (благодаря возможности использовать его в случаях, когда возврат невозможен, например, сопоставление с образцом).

Точно.

Лукаш Нимьер
[email protected]

Wiadomość napisana przez Christopher Serr [email protected] w dniu 02.09.2016, o godz. 21:05:

@hauleth https://github.com/hauleth The? В этом случае оператор будет просто синтаксическим сахаром для and_then. Таким образом, вы можете использовать его в гораздо большем количестве случаев, и не будет сложно пропустить возврат. Это также то, что есть на всех других языках, у которых есть? оператор. Ржавчины? Оператор в текущей реализации будет полной противоположностью тому, что делают все другие языки. Также and_then - это функциональный подход, который в любом случае приветствуется, поскольку он имеет четкий поток управления. Так что просто делаем? синтаксический сахар для and_then, а затем сохранить текущую попытку! для явного «разворачивания и возврата», кажется, намного более чистая ситуация, делая возвраты более заметными и? Оператор более гибкий (благодаря возможности использовать его в случаях, когда возврат невозможен, например, сопоставление с образцом).

-
Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую, просмотрите его на GitHub https://github.com/rust-lang/rust/issues/31436#issuecomment -244461722 или отключите поток https://github.com/notifications/unsubscribe-auth/ AARzN5-w4EO9_FwNMDpvtYkGUuQKGt-Kks5qmHOHgaJpZM4HUm_-.

И когда я работал над Pull Request для репозитория Rust, мне приходилось работать с кодом, который использовал? оператор и на самом деле мне это очень мешало удобочитаемости, как будто; был очень скрыт (мысленно, потому что это просто шум, который отфильтровывается в мозгу), и я часто не обращал на него внимания. И я нахожу это довольно пугающим.

@steveklabnik мы называем это «неявное возвращение», потому что мы не в только те .

@hauleth, да, за все годы моей работы с Ruby я ни разу не слышал, чтобы кто-нибудь называл это неявным возвратом. Я все еще считаю, что думать об этом неправильно.

Я использовал ? в нескольких проектах и ​​предпочел его try! , в основном потому, что он находится в постфиксной позиции. В общем, «настоящий» код Rust в значительной степени входит в монаду Result 'monad' в main и никогда не покидает ее, за исключением случайных листовых узлов; и ожидается, что такой код всегда будет распространять ошибки. По большей части не имеет значения, какие выражения генерируют ошибки - все они просто отправляются обратно в стек, и я не хочу видеть это, когда читаю основную логику кода.

Моя главная проблема с ? заключается в том, что я мог бы получить такое же преимущество - положение постфикса - с помощью макросов методов, если они существуют. У меня есть и другие опасения, что, возможно, стабилизируя текущую формулировку, мы ограничиваем будущую выразительность обработки ошибок - текущего преобразования Result недостаточно, чтобы сделать обработку ошибок в Rust такой эргономичной, как хотелось бы; мы уже сделали несколько ошибок проектирования с обработкой ошибок Rust, которые трудно исправить, и это может загнать нас глубже; хотя у меня нет конкретных доказательств.

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

Таким образом, я даю огромный +1 к стабилизации ? на основе лучше названного признака Carrier который в идеале также охватывает некоторые другие случаи, которые я поднял в другом обсуждении этого вопроса.

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

Может для этого нужен RFC? Большинству людей нравится функциональность?, Но не? сам.

Может для этого нужен RFC? Большинству людей нравится функциональность?, Но не? сам.

Это RFC с большим количеством обсуждений . Кроме того, я не знаю, откуда вы берете это «большинство людей». Если это от участников этой проблемы, вы, конечно, увидите, что больше людей будут возражать против, потому что стабилизация _ уже_ является действием команды по умолчанию.
? активно обсуждался до того, как RFC был объединен, и как сторонник довольно утомительно делать то же самое, когда обсуждается стабилизация.

В любом случае, я поставлю здесь свой +1 за мнение @mituhiko .

Для этого есть RFC, в котором много обсуждается. Кроме того, я не знаю, откуда вы берете это «большинство людей». Если это от участников этого вопроса, вы, конечно, увидите, что больше людей будут возражать против, потому что стабилизация уже является действием команды по умолчанию.

Извините, мой комментарий был слишком кратким. Я имел в виду создание RFC для каких-то «макросов методов», например func1().try!().func2().try!() (насколько мне известно, в настоящее время это невозможно).

Лично мне нравится? оператора, но я разделяю те же опасения, что и @brson , и я думаю, что было бы хорошо изучить альтернативы, прежде чем мы стабилизируем эту функцию. Включая преобразование RFC, этот поток и внутренний поток, связанный с @nikomatsakis , определенно по-прежнему существуют некоторые разногласия по поводу этой функции, даже если это одни и те же аргументы снова и снова. Однако, если жизнеспособных альтернатив нет, стабилизация действительно имеет смысл.

Кажется преждевременным стабилизировать функцию, не реализовав ее полностью - в данном случае выражение catch {..}.

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

@mcpherrinm В других языках вместо этого есть скрытые пути operator() «оператором условного возврата»?

Что касается бюджета сложности, то он только синтаксически отличается от try! , по крайней мере, в той части, на которую вы жалуетесь.
Аргумент против try! тяжелый код, который ? только делает более читабельным?
Если да, то я согласен, если есть серьезная альтернатива, кроме «вообще не иметь никакой автоматизации распространения ошибок».

Предлагаем компромисс: https://github.com/rust-lang/rfcs/pull/1737

Возможно, у него нет шансов быть принятым, но я все равно пытаюсь.

Мне нравится идея @keeperofdakeys о "макросах методов". Я не думаю, что синтаксис ? следует принимать по той же причине, по которой тернарный оператор не находится в удобочитаемости. Сама ? ничего не говорит. Вместо этого я бы предпочел возможность обобщить поведение ? с помощью «макросов методов».

a.some_macro!(b);
// could be syntax sugar for
some_macro!(a, b); 
a.try!();
// could be syntax sugar for
try!(a); 

Таким образом, будет понятно, каково поведение, и это позволит упростить цепочку.

Макрос метода, такой как result.try!() кажется более общим улучшением языковой эргономики и кажется менее специфичным, чем новый оператор ? .

@brson

У меня есть другие опасения, что, возможно, стабилизируя текущую формулировку, мы ограничиваем будущую выразительность обработки ошибок - текущего преобразования Result недостаточно, чтобы сделать обработку ошибок в Rust такой эргономичной, как хотелось бы

Это интересный момент. Стоит потратить на это некоторое время (возможно, мы с вами немного поговорим). Я согласен, здесь мы могли бы добиться большего успеха. Предлагаемый дизайн для признака Carrier (см. Https://github.com/rust-lang/rfcs/issues/1718) может здесь помочь, особенно в сочетании со специализацией, поскольку он делает вещи более гибкими.

Я действительно сомневаюсь, что макросы методов станут хорошим расширением языка.

macro_rules! в настоящее время объявляются аналогично свободным функциям и станут еще более аналогичными, когда для них будет принята новая система импорта. Я имею в виду, что они объявляются как элементы верхнего уровня и вызываются как элементы верхнего уровня, и вскоре они также будут импортированы как элементы верхнего уровня.

Методы работают не так. У методов есть следующие свойства:

  1. Не может быть объявлено в области модуля, но должно быть объявлено в блоке impl .
  2. Импортируются с типом / признаком, с которым связан блок impl , а не напрямую.
  3. Отправляются на основе типа получателя, а не на основе того, что они являются одним однозначным символом в этой области.

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

По этим причинам я не считаю хорошим выбором откладывать ? полагая, что когда-нибудь могут появиться «макросы метода».

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

@hauleth , @CryZe

Чтобы ответить тем, кто предлагает, чтобы ? был оператором and_then , это хорошо работает на таких языках, как Kotlin (я не знаком с coffeescript) из-за их широкого использования функций расширения, но это не так. так прямолинейно в ржавчине. По сути, в большинстве случаев and_then не maybe_i.and_then(|i| i.foo()) , это maybe_i.and_then(|i| Foo::foo(i)) Первое можно выразить как maybe_i?.foo() а второе - нет. Можно сказать, что Foo::foo(maybe_i?, maybe_j?) превращается в maybe_i.and_then(|i| maybe_j.and_then(|j| Foo::foo(i, j))) но это еще более сбивает с толку, чем просто сказать, что ржавчина рано возвращается при попадании в первый ? который оценивается как ошибка. Однако это, возможно, было бы более мощным.

@Stebalien В принятом RFC catch { Foo::foo(maybe_i?, maybe_j?) } делает то, что вы хотите.

@eddyb Хорошее замечание. Думаю, я могу опустить «Однако это, возможно, было бы более мощным». Все сводится к неявной ловле / явной попытке по сравнению с явной ловлей / неявной попыткой:

let x: i32 = try Foo::foo(a?.b?.c()?));
let y: Result<i32, _> = Foo::foo(a?.b?.c()?);

Против:

let x: i32 = Foo::foo(a?.b?.c()?);
let y: Result<i32, _> = catch  Foo::foo(a?.b?.c()?);

(синтаксис по модулю)

@Stebalien Другой пример: если бы я хотел передать Foo функции bar , с вашим предложением мне нужно было бы:

bar(Foo::foo(a?.b?.c()?)?)

Вы это имеете в виду? Обратите внимание на дополнительный ? , без него bar получил бы Result вместо Foo .

@eddyb Наверное. Примечание: на самом деле я этого не предлагаю! Я утверждаю, что использование ? в качестве оператора конвейера не особенно полезно в ржавчине без какого-либо способа обработки случая Foo::foo(bar?) .

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

@Stebalien с ? качестве оператора канала Foo::foo(bar?) будет выглядеть так: Foo::foo(try!(bar)) и bar(Foo::foo(a?.b?.c()?)?) (при условии, что Foo::foo : fn(Result<_, _>) -> Result<_, _> ): bar(try!(Foo::foo(a?.b?.c()?))) .

@hauleth, моя точка зрения заключалась в том, что Foo::foo(bar?)? _ намного_ чаще встречается в ржавчине, чем bar?.foo()? . Следовательно, чтобы быть полезным, ? должен поддерживать этот случай (или должна быть введена какая-то другая функция). Я постулировал способ сделать это и показал, что это, по крайней мере, будет беспорядочно. Весь смысл ? состоит в том, чтобы избежать написания try!(foo(try!(bar(try!(baz()))))) (двойные скобки!); обычно невозможно переписать это как try!(baz()?.bar()?.foo()) .

Но всегда можно:

try!(baz().and_then(bar).and_then(foo))

Лукаш Нимьер
[email protected]

Wiadomość napisana przez Steven Allen [email protected] w dniu 05.09.2016, o godz. 15:39:

@hauleth https://github.com/hauleth, я хотел сказать, что Foo :: foo (bar?)? встречается гораздо чаще, чем bar? .foo ()? в ржавчине. Следовательно, чтобы быть полезным,? должен поддержать этот случай (или потребуется ввести какую-либо другую функцию). Я постулировал способ сделать это и показал, что это, по крайней мере, будет беспорядочно. Вся суть? состоит в том, чтобы избежать записи try! (foo (try! (bar (try! (baz ()))))) (двойные скобки!); обычно невозможно переписать это как try! (baz () ?. bar () ?. foo ()).

-
Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую, просмотрите его на GitHub https://github.com/rust-lang/rust/issues/31436#issuecomment -244749275 или отключите поток https://github.com/notifications/unsubscribe-auth/ AARzN1Hdk6uk5-SoYawtgAbJUDf_8MsMks5qnBumgaJpZM4HUm_-.

В немного связанной заметке, кажется, что? -Функция в основном используется строителями, поэтому мы могли бы избежать необходимости? -Функции, предоставив простой способ создания построителей, которые обертывают Результат. Я кое-что предложил здесь, но, возможно, придется немного поработать.

https://github.com/colin-kiegel/rust-derive-builder/issues/25

Спасибо за ваши мысли по поводу идеи макроса метода @nrc и @withoutboats , приятно услышать некоторые конкретные причины, по которым они не работают.

@nielsle Я не думаю, что будет правильным сказать, что ? "в основном" используется строителями. Хотя конструкторы являются примером, в котором, как мне кажется, действительно проявляется преимущество легкого постфиксного оператора, я предпочитаю ? try! в каждом контексте.

@nielsle относительно фьючерсов, я изначально беспокоился по аналогичным причинам. Но, подумав об этом, я думаю, что async / await заменит любую потребность в ? в этом контексте. На самом деле они довольно ортогональны: вы можете делать такие вещи, как (await future)?.bar() .

(Может быть, было бы неплохо иметь оператор суффикса вместо ключевого слова await так что скобки не нужны. Или, может быть, будет достаточно тщательно настроенного приоритета.)

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

@cbreeden Я знаю, что @steveklabnik обычно избегает документирования нестабильных функций, так как есть вероятность, что это будет пустой тратой времени, если они никогда не будут стабилизированы. Не знаю, блокировали ли мы когда-нибудь стабилизацию при написании документации.

@solson Вы правы, наверное, здесь не место поднимать этот вопрос - по крайней мере, это не должно иметь отношения к вопросам стабилизации. Думаю, я просто представлял себе ситуацию, когда мы могли бы решить стабилизировать функцию, но при этом также потребовали бы документацию перед выпуском в стабильную версию rustc. Существует RFC, связанный с интеграцией документации со стабилизацией и выпуском функций, поэтому я просто подожду, пока этот процесс стабилизируется (но, конечно, не без надлежащей документации)

Я думаю, что важная часть этого RFC - иметь что-то вроде правой стороны выражения, которое действует как try! , потому что это делает чтение последовательного / связанного использования "try" _much_ более читабельным и "catch" . Первоначально я был на 100% сторонником использования ? качестве синтаксиса, но недавно я наткнулся на некоторый (чистый!) Код, уже использующий ? который заставил меня понять, что снаружи из простых примеров ? очень легко не заметить . Это заставляет меня поверить, что использование ? качестве синтаксиса для «новой попытки» может быть большой ошибкой.

Поэтому я предполагаю, что было бы неплохо провести опрос перед его завершением (с уведомлением об этом на форумах), чтобы получить обратную связь об использовании ? или какого-либо другого символа (ов) в качестве синтаксиса. . Оптимально с примером использования в более длинной функции. Обратите внимание, что я только подумываю, что было бы неплохо переименовать ? чтобы ничего не менять. В опросе можно было бы перечислить возможные другие имена, которые всплывали в прошлом, например ?! (или ?? ) или что-то вроде «использовать? Vs. использовать больше, чем на символ».

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

Кроме того, я думаю, что с помощью такого опроса можно будет связаться с людьми, обычно не участвующими в процессе RFC. Обычно это может не понадобиться, но try! => ? - это очень большое изменение для любого, кто пишет и / или читает код ржавчины.

PS:
Я изложил суть понравившейся выше функции в различных вариантах («?», «?!», «??»), но не знаю, было ли их больше. Также был RFC для переименования ? в ?! который был перенаправлен на это обсуждение.

Извините, по поводу возможного перезапуска идет уже давно идущее обсуждение: smiley_cat:.

(Обратите внимание, что ?? - это плохо, если вы все же хотите ввести ? для Option, потому что expr??? будет неоднозначным)

? очень легко не заметить

Что мы здесь обсуждаем? Полностью невыделенный код? Регулярный просмотр выделенного кода?
Или активно ищите ? в функции?

Если я выберу один ? в своем редакторе, _все_ остальные ? в файле будут выделены ярко-желтым фоном, и функция поиска также будет работать, поэтому я не вижу последний. представляя для меня _любые_ трудности.

Что касается других случаев, я бы предпочел решить эту проблему путем лучшего выделения, чем в итоге использовать ?? или ?! .

@dathinab Я думаю .try! () или что-то еще было бы лучше, но для этого потребуется UFCS для макросов.

let namespace = namespace_opt.ok_or(Error::NoEntry).try!();

Таким образом, его трудно пропустить, но набирать так же легко, а то и легче, чем .unwrap() .

@eddyb : речь идет о нормальном выделенном коде, например, на github. Например, при чтении кодовой базы. С моей точки зрения, это кажется неправильным, если мне нужно сильное выделение, чтобы не упустить из виду ? который одновременно вводит другой путь возврата (/ path to catch) и, возможно, изменяет тип переменной с Result<T> до T

@CryZe : Я согласен с вами, но не думаю, что мы сможем это сделать в ближайшем будущем. И иметь что-то, что немного короче, чем .try!() тоже не так уж и плохо.

@CryZe Мне тоже нравится этот синтаксис, но @withoutboats упомянул несколько веских причин, по которым макросы методов могут повредить язык.
С другой стороны, я боюсь, что если макросы методов действительно появятся, я не думаю, что они будут хорошо работать с ? .

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

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

@CryZe : Может быть, что-то вроде ?try будет своего рода ключевым словом. Но, честно говоря, мне это не нравится ( ?try ).

@eddyb

и функция поиска тоже работает

Поиск ? приведет к ложным срабатываниям, в то время как более длинные версии, скорее всего, этого не сделают.

Ищете? будут давать ложные срабатывания, в то время как более длинные версии, скорее всего, нет.

Я ожидаю, что подсветка синтаксиса позаботится об этом изнутри (избегая ложных срабатываний). Фактически, они могут даже вставить некоторую форму маркера «мог бы вернуть» на краю (рядом с номерами строк).

Например,
screen-2016-09-15-175131

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

2016-09-15 23:52 GMT + 02: 00 Стивен Аллен [email protected] :

Например,
[image: screen-2016-09-15-175131]
https://cloud.githubusercontent.com/assets/310393/18568833/1deed796-7b6d-11e6-99af-75f0d7ddd778.png

-
Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/rust-lang/rust/issues/31436#issuecomment -247465972,
или отключить поток
https://github.com/notifications/unsubscribe-auth/ABYmbjyrt07NXKMUdmlBfaciRZq7uBVEks5qqb4sgaJpZM4HUm_-
.

@CryZe Я почти уверен, что GitHub просто использует подсветку синтаксиса с открытым исходным кодом; мы можем исправить это :-).

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

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

Возвращаясь к исходной мотивации, try!(...) навязчив и мешает игнорировать поток ошибок и просто читать счастливый путь. Это обратная сторона по сравнению с невидимыми исключениями традиционных языков. Расширение ? на более активное ключевое слово имело бы тот же недостаток.

С другой стороны, с ? , когда меня не волнует поток ошибок, я могу игнорировать его и позволить ему исчезнуть в фоновом режиме. И когда меня действительно волнует поток ошибок, я все равно вижу ? порядке. Мне даже не нужно ярко выделять ? , но если это помогает другим людям, это здорово. Это улучшение по сравнению с невидимыми исключениями и try! .

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

Спасибо всем за сердечный заключительный период комментариев (а также за предыдущую внутреннюю ветку ). Учитывая, что мы говорим о самом комментируемом RFC на сегодняшний день , я не удивлен, увидев, что обсуждение стабилизации также было довольно активным.

Позвольте мне сначала перейти к делу: команда @ rust-lang / lang решила стабилизировать оператор ? при применении к значениям типа Result . Обратите внимание, что функция catch не стабилизируется (и, действительно, еще не реализована); аналогично, так называемая «характеристика носителя», которая является средством расширения ? на такие типы, как Option , все еще находится сможем добавить признак Carrier позже (который устраняет некоторые из моих предыдущих опасений по поводу потенциального взаимодействия с логическим выводом).

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

Область действия оператора ? должна быть текущим выражением, а не текущей функцией.

Когда происходит ошибка, сегодняшний макрос try! безоговорочно передает эту ошибку вызывающей функции (другими словами, он выполняет return с ошибкой). В соответствии с текущей разработкой, оператор ? следует этому прецеденту, но с намерением поддерживать ключевое слово catch которое позволяет пользователю указать более ограниченную область действия. Это означает, например, что x.and_then(|b| foo(b)) может быть записано как catch { foo(x?) } . Напротив, в некоторых недавних языках оператор ? для обозначения чего-то более аналогичного and_then , и были опасения, что это может запутать новых пользователей.

В конечном итоге, после реализации catch это вопрос значений по умолчанию . И есть несколько причин, по которым мы считаем, что значение по умолчанию «выход из строя» (с возможностью настройки) более подходит для ? в Rust :

  • макрос try! многократно зарекомендовал себя как очень полезный по умолчанию . ? предназначен для замены try! , поэтому его поведение естественно.
  • ? в Rust в основном используется для распространения результатов , что более похоже на исключения. Все системы исключений по умолчанию распространяют ошибки из текущей функции в вызывающую программу (даже те, которые, как Swift, также требуют для этого ключевое слово).

    • напротив, ? в Swift, Groovy и т. д. имеет отношение к нулевым значениям или типам параметров.

    • если мы примем черту носителя, оператор ? в Rust все равно можно будет использовать в указанных сценариях (и, в частности, в сочетании с catch ), но это не вариант использования _main_.

  • многие способы использования and_then в Rust сегодня не работают с использованием нотации методов ; некоторые предполагаемые будущие применения (например, во фьючерсах) в любом случае могут

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

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

Почему не макросы методов?

Одним из больших преимуществ ? является то, что его можно использовать в позиции после исправления, но
мы могли бы получить аналогичные преимущества от «макросов методов», таких как foo.try! . Хотя это и правда, макросы методов сами по себе создают большую сложность , особенно если вы хотите, чтобы они вели себя как методы (например, отправлялись на основе типа получателя, а не с использованием лексической области видимости). Более того, использование макроса метода, такого как foo.try! дает значительно больший вес, чем foo? (см. Предыдущий пункт).

Какие контракты должны предлагать From ?

В первоначальном обсуждении RFC мы решили отложить вопрос [должны ли быть «контракты» для From ] ((https://github.com/rust-lang/rust/issues/31436#issuecomment -180558025). По общему мнению, команда разработчиков lang считает, что оператор ? следует рассматривать как вызывающий свойство From , а From может естественным образом делать все, что угодно. разрешено их сигнатурами типа. Обратите внимание, что роль признака From здесь в любом случае весьма ограничена: он просто используется для преобразования из одного вида ошибок в другой (но всегда в контексте Result ).

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

порт попробовать! использовать ?

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

@nrc Почему у него нет обратной совместимости?

@withoutboats try!(x) - это (x : Result<_, _>)? и мы, вероятно, могли бы реализовать это таким образом, если бы _захотели_, но в целом x? может означать все, что поддерживает Carrier trait (в будущем), одним из примеров является iter.collect()? который с try! будет только Result но реально может быть Option .

В этом есть смысл. Я думал, мы согласились с тем, что добавление impls в std может вызвать неоднозначность вывода; почему бы также не принять это здесь?

В любом случае, я считаю, что try! не рекомендуется.

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

@ est31 Прямо сейчас они делают то же самое, за исключением вывода. Не уверен, что именно вы имеете в виду, но ? обычно строго чище (опять же по модулю). Не могли бы вы привести примеры?

@eddyb foo()?.bar()?.baz() лучше, чем try!(try!(foo()).bar()).baz() , а try!(bar(try!(foo()))) лучше, чем bar(foo()?)?

Я считаю ? более читабельным в обоих случаях. Это уменьшает ненужный беспорядок в скобках.

Когда это станет стабильным?

@ofek - это все, что еще не завершено, поэтому трудно сказать. https://github.com/rust-lang/rust/pull/36995 стабилизировал базовый синтаксис ? , который должен быть стабильным в версии 1.14.

Не забудьте добавить? на ссылку, теперь, когда она стабильна: https://doc.rust-lang.org/nightly/reference.html#unary -operator-expressions

И @bluss указал, что книга тоже устарела: https://doc.rust-lang.org/nightly/book/syntax-index.html

@tomaka

Я против продления? в Параметры.

Его не нужно использовать для ошибок. Например, если у меня есть метод-оболочка для взятия get и его сопоставления какой-либо функцией, тогда я хотел бы иметь возможность распространить случай None вверх.

? был представлен как просто за ошибки; более общие обозначения «делать» для общего распространения вроде этого не были целью.

29 октября 2016 г., 11:08 -0400, ticki [email protected] написал:

@tomaka (https://github.com/tomaka)

Я против продления? в Параметры.

Его не нужно использовать для ошибок. Например, если у меня есть метод-оболочка для получения и сопоставления его какой-либо функцией, тогда я хотел бы иметь возможность распространить случай None вверх.

-
Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую, просмотрите его на GitHub (https://github.com/rust-lang/rust/issues/31436#issuecomment-257096575) или отключите поток (https://github.com/notifications/unsubscribe -auth / AABsipGIpTF1-7enk-z_5JRYYtl46FLPks5q42DCgaJpZM4HUm_-).

Это (= ? само по себе) теперь стабильная функция! (Из Rust 1.13)

Две проблемы с документацией:

  • [x] Обновление главы об обработке ошибок в книге № 37750
  • [x] Обновить Carrier trait docs для текущей ситуации # 37751

Обратите внимание, что ни catch ни признак Carrier еще не реализованы должным образом, а только простая функция ? .

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

35946 следует удалить любое упоминание Carrier из сообщений об ошибках. Похоже, нам следует хотя бы удалить упоминание Option из Carrier документов.

Я добавляю T-libs к этой проблеме из-за взаимодействия с трейтом Carrier

Вопрос, с которым столкнулся https://internals.rust-lang.org/t/grammatical-ambiguity-around-catch-blocks/4807

Привет; в # 31954 преобразование типа ошибки выполняется с использованием From (и аналогично тому же в текущей главе ), но RFC 243 четко указывает, что для преобразования следует использовать Into .

Есть ли причина, по которой вместо этого использовалось From ? При попытке повышения до некоторого общего типа ошибки (например, io::Error или чего-то еще во внешнем ящике) From не может быть реализовано для локальных типов ошибок.

(FWIW, как автор RFC 243, я не особо задумывался о том, предпочтительнее ли From или Into , и, возможно, сделал правильный выбор, а может и нет. Это просто означает, что вопрос должен решаться на основании достоинств (которые на данном этапе могут включать обратную совместимость), а не того, что написано в RFC.)

К сожалению, будет регресс. Если нет (нетривиального) экземпляра From<...> для типа ошибки, компилятор может вывести тип в некоторых случаях, когда он не может вывести его при использовании Into (набор From Экземпляры Into неизвестен при компиляции контейнера).

См. Https://play.rust-lang.org/?gist=6d3ee9f93c8b40094a80d3481b12dd00 ("упрощено" из реальной проблемы с fmt::Error в src / librustc / util / ppaux.rs # L81 )

Есть новый RFC, который заменит старый с точки зрения описания ? : rust-lang / rfcs / pull / 1859

Я -1 к тому, чтобы иметь блоки catch вообще, когда они уже намного лаконичнее с замыканиями.

Замыкания мешают операторам управления потоком. ( break , continue , return )

Теперь, когда https://github.com/rust-lang/rfcs/pull/1859 был объединен и утверждает, что мы повторно используем эту проблему в качестве проблемы отслеживания, я хотел бы предложить, чтобы мы переоценили catch часть RFC 243.

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

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

Функция была реализована каждую ночь с синтаксисом do catch , не так ли?

@withoutboats Да, в настоящее время это do catch , поскольку catch { ... } конфликтует со структурными литералами ( struct catch { }; catch { } ).
.
@archshift Как @SimonSapin указал выше, замыкания мешают break , continue и return .

@bstrie Я обнаружил, что в наши дни довольно часто хочу catch ; это часто возникает во время рефакторинга.

Я не понимал, что мы планировали требовать do catch в качестве синтаксиса. Учитывая, что риск реальной поломки кажется чрезвычайно низким (оба нарушают правила именования структур и должны, чтобы конструктор был первым выражением в операторе (что редко за пределами позиции возврата)), возможно, мы можем использовать rustfmt для переписать любые оскорбительные идентификаторы на catch_ ? Если для этого требуется Rust 2.0, тогда, ну, я всегда был одним из тех, кто говорил, что Rust 2.0 в любом случае должен содержать только тривиальные критические изменения ...: P

@bstrie Мы абсолютно не хотим требовать do catch долгосрочной перспективе. Обсуждение, которое привело к использованию этого синтаксиса на данный момент, находится здесь: https://github.com/rust-lang/rust/pull/39921

Отлично, спасибо за контекст.

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

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

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

FWIW, C # на самом деле поддерживает противоположное: @ для использования ключевых слов в качестве идентификаторов. Это обычно наблюдается в Razor, где вы передаете такие вещи, как new { <strong i="6">@class</strong> = "errorbox" } чтобы установить свойства на узлах HTML.

@scottmcm
Это интересно. Я не знал об этом. Но для Rust мы должны пойти другим путем из-за совместимости.

Однако отличная идея с их стороны. Экосистема .net имеет множество языков, все с разными ключевыми словами, и все они могут звонить друг другу.

Еще одна возможность для ключевых слов в будущем: поместить их за атрибутом, как какой-то стабильный #[feature] .

Я думаю, что в долгосрочной перспективе эта проблема потребует более общего решения.

@camlorn Эта тема может вас заинтересовать, особенно идея Аарона об «эпохах» Rust: https://internals.rust-lang.org/t/pre-rfc-stable-features-for-breaking-changes/5002

Я думаю, мы должны связать https://github.com/rust-lang/rust/issues/42327 из описания проблемы здесь. (Также, возможно, следует обновить текст RFC, чтобы разместить ссылку там, а не здесь.)

(РЕДАКТИРОВАТЬ: я разместил там комментарий, не уверен, кто на него подписан или нет!)

@camlorn Однако он может открыть путь «rustfmt выполняет тривиальную перезапись, а затем становится ключевым словом». AKA воспользуется преимуществом «это не сломается, если есть дополнительная аннотация, которая могла бы быть написана, которая заставила бы его работать в обоих» аварийный люк в гарантии стабильности. И, подобно UFCS для изменений логического вывода, гипотетическая модель «хранилища в полностью разработанной форме» может поддерживать работу старых ящиков.

Я бы не возражал, если бы синтаксис для блока catch был просто do { … } или даже ?{ … }

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

это тоже похоже, но ведет себя иначе, чем предлагаемые в javascript выражения do

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

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

У него есть сомнительное (в зависимости от вашей точки зрения) свойство вызова нотации типа Haskell.

@rpjohnst Result и Option - это монады, поэтому они, по крайней мере, концептуально похожи. Он также должен иметь прямую совместимость, чтобы в будущем расширить его для поддержки всех монад.

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

В то же время, хотя мы, вероятно, могли бы сделать его прямой совместимой с полной нотацией до, нам, вероятно, не следует добавлять полную нотацию до. Он не сочетается с управляющими структурами, такими как if / while / for / loop или break / continue / return , который мы должны иметь возможность использовать внутри и между блоками catch (или в данном случае do ). (Это связано с тем, что полная do-нотация определяется в терминах функций более высокого порядка, и если мы поместим содержимое блока catch в серию вложенных замыканий, внезапно весь поток управления прервется.)

Таким образом, в конечном итоге обратная сторона do заключается в том, что она выглядит как do-notation, но не является do-notation, и без хорошего пути к тому, чтобы do но это путаница.

@nikomatsakis , # 42526 объединен, вы можете отметить его как выполненное в списке отслеживания :)

Можно ли сделать ключевое слово catch контекстным, но отключить его, если struct catch находится в области действия, и выдать предупреждение об устаревании?

Не уверен, насколько это уместно, но я столкнулся с проблемой, которую, возможно, необходимо решить, поскольку иногда вы хотите отменить значение ОК, а не значение ошибки, что в настоящее время возможно, когда вы используете return если вы часто хотите прервать _внутреннюю_ ошибку с помощью _внешнего_ ОК. Например, когда функция say возвращает Option<Result<_,_>> что довольно часто, скажем, от итератора.

В частности, у меня есть макрос в проекте, который я сейчас активно использую:

macro-rules! option_try {
    ( $expr:expr ) => {
        match $expr {
            Ok(x)  => x,
            Err(e) => return Some(Err(e.into())),
        }
    }
}

Очень часто функция, вызываемая в реализации Iterator::next должна быть немедленно прервана с помощью Some(Err(e)) в случае ошибки. Этот макрос работает внутри обычного тела функции, но не внутри блока catch, потому что catch не захватывает return категорически, а только специальный синтаксис ? .

Хотя, в конце концов, маркированные возвраты сделают всю идею блока catch избыточной, не так ли?

Похоже, что # 41414 готов. Может кто-нибудь обновить ОП?

Обновление: RFC rust-lang / rfcs # 2388 теперь объединен, поэтому catch { .. } следует заменить на try { .. } .
См. Проблему отслеживания прямо над этим комментарием.

Что это означает для Edition 2015: синтаксис do catch { .. } все еще находится на пути к стабилизации, или он будет удален и будет поддерживаться только через try { .. } в версии 2018+?

@ Nemo157 Последний.

Есть ли в текущем предложении два утверждения try ... catch ... ? Если так, то я не понимаю семантики.

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

Когда все флажки отмечены в верхнем посте, когда мы будем двигаться дальше? Если есть еще нерешенные проблемы, нам нужно добавить новые флажки.

Вероятно, есть еще много нерешенных вопросов, которые не были записаны.
Например, поведение ok-wrapping не определено внутри команды lang, дизайн Try не доработан и так далее. Вероятно, нам следует разделить эту проблему на несколько более целенаправленных, поскольку она, вероятно, изжила себя.

Хм ... меня беспокоит, что эти вопросы не записаны.

@ mark-im Итак, чтобы уточнить, я думаю, они где-то были; но не в одном месте; это немного разбросано по различным RFC, выпускам и тому подобному, поэтому нам нужно записать их в надлежащих местах.

Дизайн поддерживающего трейта отслеживается в https://github.com/rust-lang/rust/issues/42327; там развернуто обсуждение слабых сторон текущего и возможного нового направления. (Я планирую сделать предварительный RFC для изменений, когда 2018 немного успокоится.)

Поэтому я думаю, что здесь осталось только try{} , и единственное известное мне разногласие по этому поводу - это вопросы, которые были урегулированы в RFC и повторно подтверждены в одной из вышеупомянутых проблем. Тем не менее, было бы хорошо иметь отчетливую проблему с отслеживанием.

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

@scottmcm Я знаю, что у @joshtriplett были опасения по поводу ОК-упаковки (отмеченной в try RFC), и я лично хотел бы ограничить break при начальной стабилизации try { .. } поэтому что вы не можете делать loop { try { break } } и тому подобное.

@Centril

так что вы не можете сделать loop { try { break } }

Прямо сейчас вы не можете использовать break в блоке без цикла, и это правильно: break следует использовать только в циклах. Чтобы досрочно покинуть блок try , стандартным способом является запись Err(e)? . и это приводит к тому, что ранние листья всегда находятся на «ненормальном» пути контроля.

Итак, мое предложение - показанный вами код должен быть разрешен, и он должен нарушить loop , а не просто оставить try .

Непосредственная выгода - когда вы видите break вы знаете, что цикл прерывается, и вы всегда можете заменить его на continue . Кроме того, это устраняет необходимость маркировать точку останова при использовании блоков try внутри loop и при выходе из цикла.

@Centril Спасибо, что подняли их.

Что касается break , я лично был бы в порядке, если бы просто сказал, что try не заботится о break и переходит в содержащий цикл. Я просто не хочу, чтобы break вообще взаимодействовали с try .

Что касается Ok -wrapping, да, я хотел бы решить эту проблему, прежде чем стабилизировать try .

@centril Да, я в курсе. Но важно помнить , что это снова снова поднимает вопрос. RFC решил иметь это , это было реализовано без него, но затем первоначальное намерение было взято _ снова_ , и реализация изменилась, чтобы следовать RFC. Поэтому мой большой вопрос заключается в том, изменились ли какие-либо существенные факты, особенно с учетом того, что это одна из самых шумных тем, которые я когда-либо видел в RFC + IRLO.

@scottmcm Конечно, как вы знаете, я согласен с сохранением Ok -wrapping;) и согласен с тем, что вопрос следует считать решенным.

Я просто хотел прокомментировать это, не уверен, что это правильно:

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

РЕДАКТИРОВАТЬ:
Я допустил ошибку.


Игнорировать это сообщение


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

fn test_try(a: u32, b: u32) {
    let div = if b != 0 {
        Some(a / b)
    } else {
        None
    };

    let x // : Option<_> // why is this type annotation necessary
    = try { div? + 1 };

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

Если это переписать, чтобы использовать закрытие вместо блока try (и в процессе освобождается автоматическая упаковка), то мы получаем

fn test_closure(a: u32, b: u32) {
    let div = if b != 0 {
        Some(a / b)
    } else {
        None
    };

    let x =  (|| (div? + 1).into())();

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

Это не требует аннотации типа, но требует, чтобы мы обернули результат.

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

@KrishnaSannasi, ваш пример на основе замыкания также имеет ошибку вывода типа ( игровая площадка ), потому что Into не ограничивает вывод, и вы не используете его нигде, что происходит позже.

Похоже, это в основном проблема с чертой Try а не с блоками try , аналогично Into он не передает информацию о типе от входов к выходу, поэтому тип вывода должен быть определен при его последующем использовании. В https://github.com/rust-lang/rust/issues/42327 есть много обсуждений этой черты, я не читал ее, поэтому не уверен, может ли какое-либо из предложений решить эту проблему.

@ Nemo157

Да, я в последний момент внес изменения в свой код, чтобы использовать его, и не тестировал это. Виноват.

Насколько мы далеки от стабилизации пробных блоков? Это единственная функция, которая мне нужна от nightly: D

@Arignir

Я считаю, что как только это будет сделано, это можно будет стабилизировать.

block try {} catch (или другие следующие иденты), чтобы оставить пространство дизайна открытым для будущего, и указать людям, как делать то, что они хотят, с помощью match

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

В сделанном мной пиаре в любом случае следует отметить этот флажок, CC @nikomatsakis

Вчера я впервые попробовал использовать это, и меня немного удивило, что это:

#![feature(try_blocks)]
fn main() -> Result<(), ()> {
    let x: () = try {
        Err(())?
    }?;
    Ok(x)
}

не компилируется из-за

error[E0284]: type annotations required: cannot resolve `<_ as std::ops::Try>::Ok == _`

Скорее мне пришлось сделать

#![feature(try_blocks)]
fn main() -> Result<(), ()> {
    let x: Result<(), ()> = try {
        Err(())?
    };
    let x = x?;
    Ok(x)
}

вместо.

Сначала это сбивало с толку, так что, возможно, стоит изменить сообщение об ошибке или упомянуть в --explain ?

Если вы переместите вопросительный знак в первом примере немного вниз, чтобы

#![feature(try_blocks)]
fn main() -> Result<(), ()> {
    let x: () = try {
        Err(())?
    };
    Ok(x?)
}

Вы получите более точное сообщение об ошибке. Ошибка возникает из-за того, что Rust не может решить, к какому типу разрешить try { ... } из-за того, насколько он общий. Поскольку он не может разрешить этот тип, он не может знать, что такое тип <_ as Try>::Ok , поэтому вы получили ту ошибку, которую сделали. (поскольку оператор ? разворачивает тип Try и возвращает тип Try::Ok ). Rust не может работать с типом Try::Ok по себе, он должен быть разрешен с помощью трейта Try и типа, который реализует этот трейт. (что является ограничением текущего способа проверки типов)

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

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

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

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

Я думаю, что https://github.com/rust-lang/rfcs/pull/2388 окончательно определил, приемлемо ли имя try . Это не открытый вопрос. Но определение трейта Try а также Ok -wrapping похоже.

Ok -wrapping уже был определен в исходном RFC, затем был удален во время реализации и, наконец, повторно добавлен позже. Я не понимаю, почему это открытый вопрос.

@rpjohnst Ну, это из-за несогласия Джоша с решением с оригинальным RFC ... :) Это для меня . См. Https://github.com/rust-lang/rust/issues/31436#issuecomment -427096703, https://github.com/rust-lang/rust/issues/31436#issuecomment -427252202 и https: // github.com/rust-lang/rust/issues/31436#issuecomment -437129491. В любом случае ... суть моего комментария заключалась в том, что try как "язык исключений" является решенным вопросом.

Вау, когда это случилось? Последнее, что я помню, это дискуссии о внутренностях. Я тоже очень против Ok-wrapping :(

Фу. Не могу поверить, что это произошло. Ok -wrapping настолько ужасен (он нарушает очень разумную интуицию, что все возвращаемые выражения в функции должны иметь тип, возвращаемый функцией). Так что да, определенно с @ mark-im на этом. Достаточно ли несогласия Джоша, чтобы оставить этот вопрос открытым и продолжить его обсуждение? Я бы с радостью поддержал его в борьбе с этим, но это не значит, что я не член команды.

Ok -wrapping, как это принято в RFC 243 (буквально тот, который определяет оператор ? , если вам интересно, когда это произошло) ничего не меняет в типах выражений, возвращаемых функцией. Вот как это определено в RFC 243: https://github.com/rust-lang/rfcs/blob/master/text/0243-trait-based-exception-handling.md#catch -expressions

Этот RFC также вводит форму выражения catch {..} , которая служит для «определения области действия» оператора ? . Оператор catch выполняет связанный с ним блок. Если исключение не генерируется, результатом будет Ok(v) где v - значение блока. В противном случае, если возникнет исключение, результатом будет Err(e) .

Обратите внимание, что catch { foo()? } по сути эквивалентно foo() .

То есть он берет блок типа T и безоговорочно оборачивает его, чтобы получить значение типа Result<T, _> . Любой оператор return в блоке полностью не затрагивается; если блок является хвостовым выражением функции, функция должна возвращать Result<T, _> .

Уже много лет это реализовано в ночное время: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=88379a1607d952d4eae1d06394b50959. Это было сделано после долгого обсуждения командой lang и ссылки из этой ветки: rust-lang / rust # 41414 (и это также ссылка в верхней части этого вопроса).

28 мая 2019 г. в 17:48:27 по тихоокеанскому времени Александр Регейро [email protected] написал:

Фу. Не могу поверить, что это произошло. Ok -упаковка такая ужасная (это
нарушает очень разумную интуицию, что все возвращаемые выражения в
функция должна иметь тип, возвращаемый функцией). Так что да, определенно
с @ mark-im на этом. Достаточно ли несогласия Джоша, чтобы
открытый вопрос и получить по нему более подробное обсуждение? Я бы с радостью поддержал его
в борьбе с этим это не значит, что вы не член команды.

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

@joshtriplett @ mark-im @alexreg

Может ли кто-нибудь из вас объяснить, почему вы находите упаковку Ok такой неприятной, или дать ссылку на то место, где это уже объяснялось ранее? Я пошел искать, но беглым взглядом ничего не увидел. У меня в этом нет лошади (я буквально прокомментировал это только потому, что видел, как все флажки отмечены, и никаких обсуждений в течение месяца), но теперь, когда я пнул это шершневое гнездо, я хочу лучше понять аргументы.

Во вторник, 28 мая 2019 г., в 15:40:47 -0700 Рассел Джонстон написал:

Ok -wrapping уже был определен в исходном RFC, затем был удален во время реализации и, наконец, повторно добавлен позже. Я не понимаю, почему это открытый вопрос.

Думаю, вы частично ответили на свой вопрос. Я не думаю, что все
участники первоначального обсуждения RFC находились на той же странице; try было
абсолютно то, чего хотели многие, но единого мнения не было
для ок-упаковки.

28 мая 2019 г., в 15:44:46 -0700 Маздак Фаррохзад написал:

В любом случае ... суть моего комментария заключалась в том, что try как "язык исключений" является решенным вопросом.

В качестве пояснения я не нахожу привлекательной метафору "исключения",
и многие попытки таких вещей, как try-fn и Ok-wrapping, кажутся
попытаться сделать язык фальшивым, имея механизм, подобный исключениям.
Но сам try , как средство отлова ? за что-то иное, чем
граница функции, имеет смысл как конструкция потока управления.

Во вторник, 28 мая 2019 г., в 23:37:33 -0700 Габриэль Смит написал:

Может ли кто-нибудь из вас объяснить, почему вы находите упаковку Ok такой неприятной?

Как одна из нескольких причин:

Во вторник, 28 мая 2019 г., в 17:48:27 -0700 Александр Регейро написал:

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

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

У меня, конечно, есть свои мысли, но не могли бы мы сейчас снова открывать эту тему? У нас только что было более 500 споров о синтаксисе, поэтому я хотел бы на время избежать мин.

Если это заблокировано в команде lang, обсуждающей это, следует снова снять флажок «решать, должны ли блоки catch« переносить »значение результата (# 41414)» (возможно, с комментарием, что он заблокирован в команде lang), чтобы люди смотрели на эта проблема отслеживания знает статус?

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

@rpjohnst Спасибо за информацию!

@yodaldevoid Джош в значительной степени резюмировал мои мысли.

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

@joshtriplett также по существу резюмировал мои взгляды: проблема catch_unwind гораздо более аналогичен) и основанных на типах рассуждений. Я действительно согласен с блоками try как с механизмом определения объема и потока управления, но не с более радикальными моментами.

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

Согласован ли синтаксис аннотаций типов? Я надеялся на какой-нибудь try { foo()?; bar()?; }.with_context(|_| failure::err_msg("foon' n' barn'")?; , который даже отдаленно не заинтересован в компиляции: error[E0282]: type annotations needed .

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=4e60d44a8f960cf03307a809e1a3b5f2

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

Каждый раз, когда я использую Rust, я продолжаю думать: «Боже, я действительно хотел бы использовать здесь блок try», но примерно в 30% случаев это потому, что я действительно хотел бы использовать try для Option s (например, Я использовал это в Scala, который использовал синтаксис for для применения к монадам в целом, но очень похож на try здесь).

Только сегодня я использовал ящик json и он предоставляет методы as_* которые возвращают параметры.

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

match s {
  "^=" => |a, b| try { a.as_str()?.starts_with(b.as_str()?) }.unwrap_or(false),
  "$=" => |a, b| try { Some(a.as_str()?.ends_with(b.as_str()?)) }.unwrap_or(false),
  // original
  "$=" => |a, b| {
    a.as_str()
      .and_then(|a| b.as_str().map(|b| (a, b)))
      .map(|(a, b)| a.starts_with(b))
      .unwrap_or(false)
    },
}

Я думаю, что с точки зрения контекста, является ли возвращаемый тип Option или Result довольно ясно, и, более того, это не имеет особого значения (что касается понимания кода). Понятно, что смысл ясен: «Мне нужно проверить, действительны ли эти две вещи, и выполнить с ними операцию». Если бы мне пришлось выбрать одну из них, я бы выбрал первый, потому что я не считаю, что есть какая-либо потеря понимания, если учесть, что эта функция встроена в более широкий контекст, поскольку try будет всегда быть.

Когда я впервые начал просматривать эту ветку, я был против обертывания Ok потому что я думал, что было бы лучше быть явным, но с тех пор я начал обращать внимание на моменты, когда я говорил: "Я бы хотел здесь можно использовать блок попытки », и я пришел к выводу, что Ok -wrapping - это хорошо.

Первоначально я думал, что упаковка Ok будет лучше в том случае, если ваш последний оператор является функцией, которая возвращает тип, реализующий Try , но разница в синтаксисе будет

try {
  fallible_fn()
}

try {
  fallible_fn()?
}

И в этом случае я снова думаю, что Ok -wrapping лучше, потому что он дает понять, что fallible_fn - это функция возврата Try , поэтому на самом деле она более явная.

Я хочу знать, что думает об этом оппозиция, и, поскольку я не вижу многих других в этой ветке, @joshtriplett.

РЕДАКТИРОВАТЬ: Я должен упомянуть, что смотрел на это только с точки зрения эргономики / понимания прочитанного. Я понятия не имею, есть ли у одного больше технических достоинств, чем у другого, с точки зрения реализации, например, более простого вывода.

Я также хотел дать try шанс для некоторого вложенного парсинга Option :

#![feature(try_blocks)]

struct Config {
    log: Option<LogConfig>,
}

struct LogConfig {
    level: Option<String>,
}

fn example(config: &Config) {
    let x: &str = try { config.log?.level? }.unwrap_or("foo");
}

Это не с

error[E0282]: type annotations needed
  --> src/lib.rs:12:19
   |
12 |     let x: &str = try { config.log?.level? }.unwrap_or("foo");
   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot infer type
   |
   = note: type must be known at this point

Самое близкое, что у меня было, было

fn example(config: &Config) {
    let x: Option<&str> = try { &**config.log.as_ref()?.level.as_ref()? };
    let x = x.unwrap_or("foo");
}

as_ref весьма неудачный. Я знаю, что Option::deref кое-кому здесь поможет, но этого недостаточно. Похоже, что в игру должна вступить соответствующая эргономика (или связанная идея).

Множественные строки тоже прискорбны.

Может ли try использовать резервный вывод Result виде целочисленных литералов? Позволит ли это с первой попытки @shepmaster вывести Result<&str, NoneError> ? Какие оставшиеся проблемы могут возникнуть - возможно, найти общий тип ошибки для ? s, в который нужно преобразовать? (Я где-то пропустил обсуждение этого вопроса?)

@shepmaster Я согласен с выводом типа. Как ни странно, я попробовал ваш точный код с несколько наивной реализацией try_ и она отлично работает: https://github.com/norcalli/koption_macros/blob/4362fba8fa9b6c62fdaef4df30060234381141e7/src/lib.rs#L23

    let x = try_! { config.log?.level? }.unwrap_or("foo".to_owned());
    assert_eq!(x, "debug");

работает нормально.

несколько наивная реализация try_

Да, но ваш вызов макроса возвращает String , а не &str , требующий владения. Вы не показываете окружающий код, но это не удастся, потому что мы не владеем Config :

fn example(config: &Config) {
    let x = try_! { config.log?.level? }.unwrap_or_else(|| String::from("foo"));
}
error[E0507]: cannot move out of captured variable in an `Fn` closure
  --> src/lib.rs:20:21
   |
19 | fn example(config: &Config) {
   |            ------ captured outer variable
20 |     let x = try_! { config.log?.level? }.unwrap_or_else(|| String::from("foo"));
   |                     ^^^^^^^^^^ cannot move out of captured variable in an `Fn` closure

Он также безоговорочно выделяет String ; В этом примере я использовал unwrap_or_else чтобы избежать этой неэффективности.

Жаль, что эта функция не была стабилизирована до блоков async/await . ИМО, было бы более последовательным, если бы

let fut = async try {
    fut1().await?;
    fut2().await?;
    Ok(())
};

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

Re: автоматическая упаковка, я не думаю, что сейчас можно будет согласовываться с блоками async . Блоки async выполняют своего рода "автоматическую упаковку" в анонимный тип, реализующий Future . Но это верно даже для ранних возвратов, что было бы невозможно с блоками try .

Если у нас когда-либо будет гипотетический блок async try это может стать вдвойне запутанным. Должно ли это автоматически обернуть результат?

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

Блок try , с другой стороны, может использовать ? с автоматическим Ok -переносом, в том числе для "ранних возвратов", предполагая возможность досрочного возврата - возможно, label-break- значение . Гипотетическая функция try может легко выполнить автоматическое обертывание Ok как при раннем, так и при окончательном возврате.

Гипотетический блок async try может просто объединить функциональность двух- авто- Ok -wrap, а затем auto- Future -wrap. (Обратный способ реализовать невозможно и, возможно, в любом случае будет написано try async .)

Я вижу несогласованность в том, что мы объединили блоки async с функциями. (Это произошло в последнюю минуту вопреки RFC, не меньше.) Это означает, что return в async блоках выходит из блока, а return в try blocks закрывает содержащую функцию. Однако они, по крайней мере, имеют смысл изолированно, и блоки async без раннего возврата или значения label-break использовать было бы намного сложнее.

Что-нибудь мешает стабилизации этого, или просто никто еще не нашел времени на это? В противном случае я заинтересован в создании необходимых PR 🙂

18 ноября 2019 г., 2:03:36 AM PST, Kampfkarren [email protected] написал:

Все, что мешает стабилизации этого, или просто никто не принял
пора это сделать? Я заинтересован в создании необходимых PR
в противном случае 🙂>
>
->
Вы получаете это, потому что вас упомянули.>
Ответьте на это письмо напрямую или просмотрите его на GitHub:>
https://github.com/rust-lang/rust/issues/31436#issuecomment -554944079

Да, блокиратор стабилизации работает через решение ок-накрутки. Это не должно стабилизироваться, пока мы не достигнем консенсуса в отношении того, как он должен себя вести.

Я лично против Ok-wrapping, но мне интересно, насколько сложно было бы добавить постфактум. Совместима ли форвардная упаковка no-ok-wrapping с ok-wrapping?

Я могу представить себе сложные случаи, такие как двусмысленность Result<Result<T,E>> , но в таких неоднозначных случаях мы могли бы просто вернуться к без упаковки. Затем пользователь может явно использовать Ok-wrap для устранения неоднозначности. Это не так уж плохо, поскольку я не ожидаю, что подобная двусмысленность будет возникать слишком часто ...

Не должно быть никакой двусмысленности, потому что это не Ok -принуждение, а Ok -обертка. try { ...; x } даст Ok(x) так же однозначно, как Ok({ ...; x }) .

@joshtriplett Это не resolve whether catch blocks should "wrap" result value как отмеченную, со ссылкой на https://github.com/rust-lang/rust/issues/41414.

@rpjohnst Извините, я должен был быть более ясным. Я имею в виду, что если мы стабилизируем try сейчас без Ok-wrapping, я считаю, что его можно было бы добавить позже с обратной совместимостью.

То есть, я думаю, большинство людей согласны с тем, что у нас должны быть блоки try , но не все согласны с catch или ок-оберткой. Но я не думаю, что эти обсуждения должны блокировать try ...

@Kampfkarren Да. Вышеупомянутый разговор подробно описывает развитие этого вопроса. Это было преждевременно снято, не посоветовавшись со всеми. В частности, у

@ mark-im Как именно вы видите добавление Ok-wrapping в будущем? Я пытаюсь понять, как это можно сделать, но не совсем понимаю.

Так что я начну с того, что скажу, что не знаю, хорошая это идея или нет ...

Мы бы стабилизировали блок try без оклейки. Например:

let x: Result<usize, E> = try { 3 }; // Error: expected Result, found usize
let x: Result<usize, E> = try { Ok(3) }; // Ok (no pun intended)

Позже предположим, что мы пришли к консенсусу, что у нас должно быть Ok-wrapping, тогда мы могли бы разрешить некоторые случаи, которые раньше не работали:

let x: Result<usize, E> = try { 3 }; // Ok
let x: Result<usize, E> = try { Ok(3) }; // Also Ok for backwards compat
let x: Result<Result<usize, E1>, E2> = try { Ok(3) }; // Ok(Ok(3))
let x: Result<Result<usize, E1>, E2> = try { Ok(Ok(3)) }; // Ok(Ok(3))

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

let x = try { Err(3) }; // If x: Result<Result<T1, usize>, usize>, then it is not clear if user meant Ok(Err(3)) or Err(3)...

Хотя, может, Ok-wrapping уже борется с этой проблемой?

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

Как насчет использования Ok-wrapping, кроме случаев, когда возвращаемый тип - Result или Option ? Это позволит упростить код в большинстве случаев, но позволит указать точное значение там, где это необходимо.

// Ok-wrapped
let v: Result<i32, _> = try { 1 };

// not Ok-wrapped since the returned type is Result
let v: Result<i32, _> = try { Ok(1) };

// not Ok-wrapped since the returned type is Result
let v: Result<i32, _> = try { Err("error") };

// Ok-wrapped
let v: Option<i32> = try { 1 };

// not Ok-wrapped since the returned type is Option
let v: Option<i32> = try { Some(1) };

// not Ok-wrapped since the returned type is Option
let v: Option<i32> = try { None };

Добавление Ok -принуждения или какого-либо вида зависимого от синтаксиса Ok -wrapping (что должно произойти для поддержки стабилизации без этого и последующего введения) было бы очень плохо для читабельности, был широко аргументирован несколько раз на i.rl.o (обычно люди неправильно понимают реализованную прямую Ok -упаковку).

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

let x = try { Err(3) }; // If x: Result<Result<T1, usize>, usize>, then it is not clear if user meant Ok(Err(3)) or Err(3)...

Хотя, может, Ok-wrapping уже борется с этой проблемой?

Нет, это однозначно Ok(Err(3)) , Ok -wrapping не зависит от синтаксиса или типов, он просто обертывает все, что вывод блока находится в варианте Try::Ok .

@ mark-im Я не думаю, что мы можем разумно переходить от одного к другому после стабилизации. Как бы плохо я ни считал Ok-wrapping, непоследовательная Ok-упаковка, которая пытается угадать, хотите вы этого или нет, была бы еще хуже.

В моей кодовой базе я имею дело с множеством необязательных значений, поэтому я давно представил свой собственный блок try, такой как макрос. И когда я его представлял, у меня были разные варианты с Ok-wrapping и без, и Ok-wrapping версия оказалась намного более эргономичной, что это единственный макрос, который я в итоге использовал.

У меня есть масса необязательных значений, с которыми мне нужно работать, и они в основном числовые, поэтому у меня бывает множество таких ситуаций:

let c = try { 2 * a? + b? };

Без Ok-wrapping это было бы гораздо менее эргономично, так что я бы, вероятно, остановился на собственном макросе, чем на реальных блоках try.

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

Без оклейки это было бы гораздо менее эргономично.

Не могли бы вы уточнить, какие именно неэргономичные вещи он привнесет?

Без Ok-обертывания ваш пример будет выглядеть так:

let c = try { Ok(2 * a? + b?) };

что, на мой взгляд, неплохо.

Я имею в виду, что в таком небольшом примере это может показаться излишним, но чем больше кода содержит блок try тем меньшее влияние оказывает эта Ok(...) оболочка.

В дополнение к комментарию @CreepySkeleton следует отметить, что очень легко создать макрос, эмулирующий Ok -wrapping, если блок try не делает этого (и кто-то обязательно создаст стандартный ящик для этого крошечного макроса), но обратное не так.

Этот макрос невозможен, пока свойство Try нестабильно.

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

@ Nemo157 try блоки также работают по ночам только сейчас, и они, вероятно, не будут стабилизированы в маловероятном случае, когда мы решим вырвать Try . Это означает, что они, скорее всего, не будут стабилизированы до Try . Поэтому утверждение, что макрос невозможен, не имеет смысла.

@KrishnaSannasi Мне любопытно, почему Try может быть вырван?

@ mark-im Я не думаю, что это будет, я просто объясняю, почему беспокойство о том, что Try еженощно пытается блокировать блоки, нереально. С нетерпением жду Try в стабильной версии.

Учитывая, что ? уже стабилизировалось, а блоки try имеют четкий дизайн, охватывающий как Result и Option же, как ? делает, я не вижу причин блокировать их стабилизацию при стабилизации Try . Я не пристально за этим следил, но у меня сложилось впечатление, что консенсуса по дизайну блоков Try было гораздо меньше, чем блоков try , поэтому я мог видеть try блокирует стабилизацию за годы до признака Try (как это произошло с ? ). И даже если черта Try будет отброшена, я не вижу причин, которые должны блокировать стабилизацию блоков try как работающих только с Result и Option например ? Тогда

(Почему вы не могли написать этот макрос с учетом стабилизированных блоков try и нестабильного признака Try , макрос расширился бы до try { Try::from_ok($expr) } ; вы могли бы создавать макросы для каждого типа всего за Result и Option , но ИМО, которые не соответствуют пункту "очень легко [...] эмулировать").

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

Я бы предложил следующую концепцию для семантической попытки поймать ...

Рассмотрим следующий код:

union SomeFunctionMultipleError {
    err0: Error1,
    err1: Error2,
}

struct SomeFunctionFnError {
    index: u32,
    errors: SomeFunctionMultipleError,
}

fn some_function() -> Result<i32, SomeFunctionFnError> {
    if 0 == 0 {
        Ok(2)
    } else {
        Err(SomeFunctionFnError{ index: 0, errors: SomeFunctionMultipleError {err1: Error2 {id0: 0, id1: 0, id3: 0}}})
    }
}

union OtherFunctionMultipleError {
    err0: Error1,
    err1: Error2,
    err2: Error3,
}

struct OtherFunctionFnError {
    id: u32,
    errors: OtherFunctionMultipleError,
}

fn other_function() -> Result<i32, OtherFunctionFnError> {
    if 0 == 0 {
        Ok(2)
    } else {
        Err(OtherFunctionFnError {id: 0, errors: OtherFunctionMultipleError {err0: Error1 {id0: 0, id1: 0}}})
    }
}

Это код, который может быть сгенерирован исключениями Zero-Overhead в Rust со следующей функцией синтаксиса:

fn some_function() -> i32 throws Error1, Error2 {
    if 0 == 0 {
        2
    } else {
        Error2 {id0: 0, id1: 0, id3: 0}.throw
    }
}

fn other_function() -> i32 throws Error1, Error2, Error3 {
    if 0 == 0 {
        2
    } else {
        Error1{id0: 0, id1: 0}.throw
    }
}

или даже эти ошибки могут быть выведены компилятором неявно:

fn some_function(i: i32) -> i32 throws { // Implicitly throws Error1, Error2
    if i == 0 {
        2
    } else if i == 1 {
        Error1 {id0: 0, id1: 0, id3: 0}.throw
    } else {
        Error2 {id0: 0, id1: 0, id3: 0}.throw
    }
}

fn other_function(i: i32) -> i32 throws { // Implicitly throws Error1
    if i == 0 {
        2
    } else {
        Error1{id0: 0, id1: 0}.throw
    }
}

Это не что иное, как синтаксический сахар !! Поведение такое же !!

Привет всем,

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

@redradist Одним из основных моментов блока try является то, что мы можем использовать его внутри функции , без необходимости создавать функцию для каждого блока. Насколько я понимаю, ваше предложение здесь совершенно не связано.

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

Кстати. Обернуть операции во внутреннюю функцию было бы сложно в этом случае, потому что контекст не имеет очевидного времени жизни, а разделение всего на несколько функций нарушает NLL.

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

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

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

#![feature(try_blocks)]

fn main() {
    let s: Result<(), ()> = try { () };
}

Конечно, вот оно .

А вот еще одно , показывающее, что вывод типа для блоков try еще не завершен. Что особенно раздражает, поскольку в блоках if let не поддерживаются описания типов.

@ Nokel81 проблема с вашим предыдущим примером заключается в том, что выражение в if let $pat = $expr не является контекстом регулярного выражения, а скорее специальным контекстом «выражение без скобок». В качестве примера того, как это работает с выражениями структуры, см. Этот пример, где синтаксически ясно, что там есть выражение структуры, а в этом примере - нет. Таким образом, ошибка заключается не в том, что try не является выражением, а в том, что ошибка неправильная и должна содержать сообщение « try выражение здесь не допускается; попробуйте заключить его в круглые скобки» (и неверное предупреждение о подавлении ненужных скобок).

Ваш последний пример на самом деле неоднозначен для вывода типа. Тип e в этом случае - _: From<usize> , что недостаточно для того, чтобы дать ему конкретный тип. Вам нужно будет каким-то образом использовать его, чтобы дать ему конкретный тип, чтобы обеспечить успешный вывод типа. Это не проблема, специфичная для try ; так работает вывод типов в Rust.

Теперь, если вы немедленно попытаетесь сопоставить как Ok и отбросить случай Err , у вас будет случай неоптимального сообщения об ошибке без реального простого способа исправить это .

Ах, большое спасибо за подробное объяснение. Думаю, я до сих пор не понимаю, почему последнее является неоднозначным для вывода типа. Почему тип выражения не Result<isize, usize> ?

Оператор ? может выполнять преобразование типов между различными типами ошибок с помощью трейта From . Он расширяется примерно до следующего кода ржавчины (игнорируя черту Try ):

match expr {
    Ok(v) => v,
    Err(e) => return From::from(e),
}

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

Приносим извинения, если это уже было решено, но мне кажется странным, что:

#![feature(try_blocks)]

fn main() -> Result<(), ()> {
    let result = try { // no type annotation
        Err(())?;
    };
    result.map_err(|err| err)
}

не компилируется с:

error[E0282]: type annotations needed

но:

#![feature(try_blocks)]

fn main() -> Result<(), ()> {
    let result : Result<_, _> = try { // partial type annotation
        Err(())?;
    };
    result.map_err(|err| err)
}

хорошо.

Если бы это была проблема, потому что аргументы типа Result не могли быть выведены, я бы понял, но, как показано выше, это не так, и rustc может выполнить вывод, как только ему будет сказано, что результат выражение try - это своего рода Result , который он должен иметь возможность вывести из core::ops::Try::into_result .

Мысли?

@nwsharp , потому что try / ? является общим для типов Try . Если бы у вас был какой-то другой тип, который был impl Try<Ok=_, Error=()> , блок try мог бы оценивать этот тип, а также Result . Desugared, ваш пример примерно

#![feature(try_trait, label_break_value)]

use std::ops::Try;

fn main() -> Result<(), ()> {
    let result /*: Result<_, _>*/ = 'block: {
        match Try::into_result(Err(())) {
            Ok(ok) => Try::from_ok(ok),
            Err(err) => {
                break 'block Try::from_error(From::from(err));
            }
        }
    };
    result.map_err(|err| err)
}

@ CAD97 Спасибо за объяснение.

Тем не менее, я не ожидал, что try будет эффективно вызывать некое преобразование между разными Try impls.

Я ожидал удаления хирургического вмешательства, при котором один и тот же Try impl выбран для into_result , from_ok и from_error .

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

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

try { ... }.into()

С соответствующим одеялом подразумевается:

impl<T: Try, E: Into<T::Err>> From<Result<T::Ok, E>> for T {
    fn from(result: Result<T::Ok, E>) -> Self {
        match result {
            Ok(ok) => T::from_ok(ok),
            Err(err) => T::from_err(err.into()),
        }
    }
}

(Что, честно говоря, имеет смысл иметь в любом случае, хотя я лично сомневаюсь в автоматическом преобразовании типов ошибок здесь. Если это необходимо, пользователь должен .map_err() на Result .)

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

Или, я полагаю, пойти еще дальше с импровизированным одеялом.

impl <T: Try, U: Try> From<U> for T 
    where U::Ok : Into<T::Ok>, U::Err : Into<T::Err>
{
    fn from(other: U) -> Self {
        match other.into_result() {
            Ok(ok) => Self::from_ok(ok.into()),
            Err(err) => Self::from_err(err.into()),
        }
    }
}

Или как там ...

Тем не менее, я не ожидал, что try будет эффективно вызывать некое преобразование между разными Try impls.

Я бы ожидал удаления хирургического вмешательства, при котором тот же Try impl выбран для into_result , from_ok и from_error .

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

Существует четыре стабильных типа Try : Option<T> , Result<T, E> , Poll<Result<T, E>> и Poll<Option<Result<T, E>> .

NoneError нестабилен, поэтому Option<T> застревает при попытке в Option<T> а NoneError нестабилен. (Обратите внимание, что в документации From<NoneError> явно указывается как «включить option? для вашего типа ошибки».)

Однако Poll impls устанавливает свой тип ошибки на E . Из-за этого "преобразование типов" Try стабильно, потому что вы можете ? a Poll<Result<T, E>> в -> Result<_, E> чтобы получить Poll<T> и досрочно вернуть кейс E .

Фактически, от этого и работает "милый" маленький помощник :

fn lift_err<T, E>(x: Poll<Result<T, E>>) -> Result<Poll<T>, E> { Ok(x?) }

@ CAD97 Спасибо, что

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

Например, велосипедная скидка на бит, try<T> { ... } . Или есть еще что-то, на что можно споткнуться?

Чтобы, возможно, добавить здесь немного больше цвета, тот факт, что try { } на кучу Result не "просто" дает Result является неожиданным и меня огорчает. . Я понимаю почему , но мне это не нравится.

Да, было обсуждение комбинации «приписывания обобщенного типа» (вот ваш термин для поиска) и try . Я думаю, последнее, что я слышал, try: Result<_, _> { .. } должен был в конечном итоге работать.

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

См. Эту отдельную проблему для конкретного узкого вопроса для достижения консенсуса языковой группы по поводу Ok -wrapping.

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

Я не понимаю, зачем нужен блок try . Этот синтаксис

fn main() -> Result<(), ()> {
    try {
        if foo() {
            Err(())?
        }
        ()
    }
}

можно заменить на это:

fn main() -> Result<(), ()> {
    Ok({
        if foo() {
            Err(())?
        }
        ()
    })
}

Оба они используют одинаковое количество символов, но второй уже стабилен.

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

Блоки @dylni try особенно полезны, когда они не содержат всего тела функции. Оператор ? при ошибке переводит управление потоком в конец самого внутреннего блока try без возврата из функции.

ржавчина
fn main () / * здесь нет результата * / {
let result = try {
foo () ?. bar () ?. baz ()?
};
результат матча {
//…
}
}

@SimonSapin Это часто

fn main() /* no result here */ {
    let result  = foo()
        .and_then(|x| x.bar())
        .and_then(|x| x.baz());
    match result {
        // …
    }
}

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

fn main() /* no result here */ {
    let result  = foo()
        .and_then(::bar)
        .and_then(::baz);
    match result {
        // …
    }
}

Тип также правильно выводится с помощью and_then , где вам нужны аннотации типа для try . У меня это возникало достаточно редко, поэтому я не думаю, что более лаконичный синтаксис стоит того, чтобы ухудшить читабельность.

В принятом RFC есть еще несколько аргументов: https://rust-lang.github.io/rfcs/0243-trait-based-exception-handling.html

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

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

@SimonSapin Спасибо. Это и повторное чтение RFC убедили меня, что это может быть полезно.

Я думал, что могу использовать блоки try, чтобы легко добавлять контекст к ошибкам, но пока безуспешно.

Я написал небольшую функцию, которая отлично работает. Обратите внимание, что File::open()? завершается ошибкой с std::io::Error а следующая строка терпит неудачу с anyhow::Error . Несмотря на разные типы, компилятор выясняет, как преобразовать оба в Result<_, anyhow::Error> .

fn tls_add_cert(config: &ClientConfig, path: impl AsRef<Path>) -> Result<(usize, usize), anyhow::Error> {
    let path = path.as_ref();
    let mut file = BufReader::new(File::open(path)?);
    Ok(config.root_store.add_pem_file(&mut file)
        .map_err(|_| anyhow!("Bad PEM file"))?)
}

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

fn tls_add_cert(config: &ClientConfig, path: impl AsRef<Path>) -> anyhow::Result<(usize, usize)> {
    let path = path.as_ref();
    try {
        let mut file = BufReader::new(File::open(path)?);
        Ok(config.root_store.add_pem_file(&mut file)
            .map_err(|_| anyhow!("Bad PEM file"))?)
    }
    .with_context(|| format!("Error adding certificate {}", path.display()))
}

Но теперь вывод типа не выполняется:

error[E0282]: type annotations needed
  --> src/net.rs:29:5
   |
29 | /     try {
30 | |         let mut file = BufReader::new(File::open(path)?);
31 | |         Ok(config.root_store.add_pem_file(&mut file)
32 | |             .map_err(|_| anyhow!("Bad PEM file"))?)
33 | |     }
   | |_____^ cannot infer type
   |
   = note: type must be known at this point
   ```

I don't understand why a type annotation is needed here but not in the first case. Nor do I see any easy way to add one, as opposed to using an [IIFE](https://en.wikipedia.org/wiki/Immediately_invoked_function_expression) which does let me add an annotation:

```rust
(|| -> Result<_, anyhow::Error> {
    let domain = DNSNameRef::try_from_ascii_str(host)?;
    let tcp = TcpStream::connect(&(host, port)).await?;

    Ok(tls.connect(domain, tcp).await?)
})()
.with_context(|| format!("Error connecting to {}:{}", host, port))

@jkugelman

Очередной раз,

это потому, что try / ? является общим для типов Try . Если у вас есть другой тип, который был [ impl Try<Ok=_, Error=anyhow::Error> ], блок try мог бы оценивать этот тип, а также Result .

(Кроме того, вам не нужно Ok конечное выражение в блоке try (# 70941).)

Я думаю, что тот факт, что это продолжает появляться, означает, что

  • Перед стабилизацией try должен поддерживать описание типа ( try: Result<_,_> { или что-то еще) или иным образом смягчать эту проблему,
  • Это определенно нуждается в целевой диагностике, когда не удается определить тип блока try , и
  • Мы должны серьезно рассмотреть возможность предоставления try запасного типа для Result<_,_> если это не ограничено иным образом. Да, это сложно, недооценено и потенциально проблематично, но это _в_ решит 80% случаев, когда try блокам требуется аннотация типа из-за того, что $12: Try<Ok=$5, Error=$8> недостаточно конкретен.

Кроме того, учитывая, что # 70941, похоже, разрешается в сторону «да, мы хотим (в какой-то форме) ' Try::from_ok wrapping'», мы, вероятно, _также_ хотим получить целевую диагностику, когда хвостовое выражение try block возвращает Ok(x) когда x будет работать.

Я подозреваю, что правильное поведение для попытки - это

  • расширить синтаксис, чтобы разрешить ручное приписывание, например try: Result<_, _> { .. } , try as Result<> или что-то еще (я думаю, что try: Result , вероятно, в порядке? кажется, это предпочтительный синтаксис)
  • изучите "ожидаемый тип", который исходит из контекста - если он присутствует, предпочтите его в качестве типа результата try
  • в противном случае по умолчанию используется Result<_, _> - это не резервный вариант вывода типа, как в случае с i32 , это произойдет раньше, но это будет означать, что компилируются такие вещи, как try { }.with_context(...) .

Однако меня беспокоит, что мы можем получить ошибки, связанные с ? и into принуждение, по крайней мере, пока тип ошибки не указан. В частности, если вы пишете код, в котором ? - результат блока try , например:

#![feature(try_blocks)]

use std::error::Error;
fn foo() -> Result<(), Box<dyn Error>> {
    let x: Result<_, _> = try {
        std::fs::File::open("foo")?;
    };

    x?;

    Ok(())
}

fn main() { 
}

Вы по-прежнему получаете ошибки ( игровая площадка ), и это правильно, потому что неясно, на каком ? должно срабатывать принуждение "into".

Я не уверен, какое здесь лучшее решение, но оно, вероятно, связано с каким-то резервным выводом типа, который заставит меня нервничать.

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

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

«(Если не указано иное)» - это, конечно, тонкая пугающая часть.

Надеюсь, я не слишком неконструктивен в этом посте. Однако я хотел сравнить пример ? не выполняет принудительное преобразование, и нет автоматического переноса результата блока try :

use std::error::Error;
fn foo() -> Result<(), Box<dyn Error>> {
    let x = try {
        std::fs::File::open("foo").err_convert()?;
        Ok(())
    };

    x?;

    Ok(())
}

В этом мире:

  • Легко видеть, что как область try и сама fn приводят к успеху без какого-либо значения. Также легко увидеть, даже не пытаясь, что они производят Result s.
  • Очевидно, где происходит преобразование ошибок.
  • Преобразование ошибок можно перенести в выражение x? , сделав область действия try специфичной для операций std::fs::File .
  • Все подсказки типа плавно цепляются из сигнатуры типа. И для машины, и для нас, людей.
  • Подсказка типа пользователем требуется только в тех случаях, когда мы действительно хотим свести ошибки в другую, независимую.

Я был бы очень счастлив в этой параллельной вселенной.

@phaylon Хотя я ценю бережное , как вы написали , что комментарий, я боюсь , что это довольно неконструктивная. Преобразование ошибок является частью ? и это не изменится, и в свете этого ок-упаковка в основном ортогональна по сравнению с остальной частью этого обсуждения.

Если когда-либо будут рассматриваться функции try (с типами return и throw), то, возможно, стоит также рассмотреть синтаксис для приписывания блока try чем-то подобным.

например

try fn foo() -> u32 throw String {
  let result = try: u32 throw String {
    123
  };
  result?
}

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

try fn foo() -> u32 throw String { ... }

или аналогичный, в отличие от

fn foo() -> Result<u32, String> { ... }

?
Похоже на повторяющийся синтаксис.

@gorilskij Насколько я понимаю, основное преимущество - получить Ok -обертку. В противном случае нужно написать:

fn foo() -> Result<u32, String> {
    try {
        // function body
    }
}

Некоторые люди также предпочитают throws как считают терминологию исключений связанной.

Лично я хочу держаться как можно дальше от появления исключений.

Это не тема для обсуждения try fn , поэтому, пожалуйста, не заходите дальше по этому поводу. Этот поток предназначен для принятой функции блоков try , а не для потенциальной (и пока еще не RFCd) функции try fn .

Я только заметил, что в исходном RFC для ? предлагается использовать Into , а не From . Говорится:

В настоящем RFC для этой цели используется трейт std::convert::Into (который подразумевает переадресацию из From ).

Хотя точный метод апкастинга остается нерешенным вопросом . Into был (предположительно) предпочтительным на основании рекомендаций From :

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

Однако в RFC для характеристики Try Into больше не упоминается, и вместо этого преобразование выполняется с использованием From . То же самое и сейчас используется в коде, даже для ?
https://github.com/rust-lang/rust/blob/b613c989594f1cbf0d4af1a7a153786cca7792c8/src/librustc_ast_lowering/expr.rs#L1232

Это кажется неудачным, поскольку не существует общей реализации, переходящей от Into к From . Это означает, что ошибки, которые реализуют Into (в отличие от From ) либо по ошибке, по устаревшим причинам, либо по какой-либо другой необходимости, не могут быть использованы с ? (или Try ). Это также означает, что любая реализация, которая следует рекомендациям стандартной библиотеки по использованию Into в границах, не может использовать ? . В качестве примера стандартная библиотека рекомендует написать:

fn with_user_err<E>(op: impl Fn() -> Result<(), E>) -> Result<(), MyError>
where E: Into<MyError>

но если я это сделаю, я не могу использовать ? в теле функции. Если я хочу это сделать, я должен написать

fn with_user_err<E>(op: impl Fn() -> Result<(), E>) -> Result<(), MyError>
where MyError: From<E>

но если я это сделаю, пользователи с типами ошибок, реализующими Into вместо From не смогут использовать эту функцию. Обратите внимание, что обратное неверно из-за бланкетного импликации Into на основе From .

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

@jonhoo @cuviper попытался изменить обессахаривание с From на Into в # 60796, чтобы проверить # 38751, и это привело к большому количеству ошибок вывода именно из-за From -> Into blanket impl усложняет для rustc обработку распространенного случая преобразования идентичности. Было решено, что такой вывод не стоит того.

@jonhoo, вы также можете найти этот комментарий от niko информативным:

В системе признаков также есть жестко запрограммированный предел. Если вам нужно решить такую ​​цель, как ?X: Into<ReturnType> , мы не сможем решить эту проблему, но если вам нужно решить такую ​​цель, как ReturnType: From<?X> , мы потенциально добьемся успеха и выведем значение для ?X .

Изменить: здесь ?X относится к некоторой неизвестной переменной вывода. Жестко запрограммированный предел в сегодняшней системе признаков состоит в том, что тип Self должен быть хотя бы частично выведен, чтобы мы могли изучить этот вариант.

TL; DR заключается в том, что вывести с помощью Into сложнее, и это связано с тем, как работает решатель признаков.

@KrishnaSannasi @ CAD97 Спасибо, это полезно! Я все еще беспокоюсь о том, что мы слишком сильно стабилизируем, основываясь на From учитывая, что мы оставляем разработчиков типа Into навсегда. Ожидается ли, что вывод здесь в конечном итоге улучшится? Следует ли изменить руководство о предпочтении Into в границах? Ожидаем ли мы, что с новыми правилами согласованности признаков в 1.41 (я так думаю) больше не будет причин для реализации только Into и рассмотрения всех этих ошибок impls?

Если логический вывод будет достаточно хорошим, он должен быть совместим с продвижением, чтобы позже перейти на Into . Худшее, что мы можем сломать, - это вывод, учитывая, что From подразумевает Into

Имеет ли это (свойство Try ) покрытие, позволяющее ? работать с трейтами Into / From с общими границами для типов, реализующих Try (например, Result сам)?

т.е. ? в закрытии или функции, которая возвращает, например, impl Into<Result>

(Не похоже, когда я пробую это по ночам?)

Я очень хочу, чтобы это стабилизировалось. Прочитав эту ветку и # 70941, я думаю, что резюме следует обновить следующим образом:

  1. «решить, должны ли блоки catch« оборачиваться »значение результата» должно быть отмечено галочкой, «Разрешено как да »

  2. Новое беспокойство добавлено по поводу этих проблем вывода. Возможно что-то вроде:

    • [] Эргономические трудности из-за проблем с выводом типа.

ISTM, что эту последнюю проблему можно решить, среди прочего:

  • Добавьте синтаксис шифрования индивидуального типа в try до стабилизации
  • Решите, что https://github.com/rust-lang/rfcs/pull/803 # 23416 (Type ascription) решит эту проблему и сначала необходимо стабилизировать
  • Добавьте какой-то автоматический откат (например, к Result , возможно, как предлагается в https://github.com/rust-lang/rust/issues/31436#issuecomment -614735806
  • Решите стабилизировать try как есть сейчас, оставив синтаксическое / семантическое пространство для дальнейшего улучшения

(Некоторые из этих вариантов не исключают друг друга.)

Спасибо за внимание и надеюсь, что это сообщение окажется для вас полезным.

Присвоение типов имеет ряд проблем (синтаксических и других) и вряд ли будет реализовано в ближайшее время, не говоря уже о стабилизации; блокировка try блоков по синтаксису описания типа не подходит.

Возврат к Result может помочь, но не решает проблемы вывода типов с типами ошибок: try { expr? }? (или на практике более сложные эквиваленты) фактически имеют два вызова .into() , что дает компилятору слишком большую гибкость для промежуточного типа.

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

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

В среду, 5 августа 2020 г., в 14:29:06 -0700 Нико Мацакис написал:

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

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

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

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

Я натыкаюсь на места, где мне бы очень хотелось использовать пробные блоки. Было бы хорошо перебороть это. Лично я бы абсолютно на 100% пожертвовал описанием типа, если бы требовалось перебросить блоки try. Мне еще предстоит оказаться в ситуации, когда я сказал бы «черт возьми, я бы хотел иметь здесь описание типов», но в конечном итоге выполняю IIFE для частой имитации блоков try. Оставлять долгосрочную, полезную функцию нестабильной из-за того, что она конфликтует с другой долгосрочной, нестабильной функцией, - действительно неудачная ситуация.

Чтобы быть немного более конкретным, я обнаружил, что делаю это, когда нахожусь внутри функции, которая возвращает Result, но я хочу выполнить некоторую обработку для вещей, которые возвращают Option. Тем не менее, если бы Try в целом был стабильным, я, вероятно, предпочел бы блоки try, поскольку на самом деле я не хочу возвращаться из основной функции для этого, а вместо этого задаю какое-то значение по умолчанию, если что-то в цепочке Никто. У меня это обычно случается в коде стиля сериализации.

Лично я хотел присвоить тип гораздо чаще, чем блоки try (хотя иногда мне хотелось и того, и другого). В частности, я часто боролся с «отладкой типов», когда компилятор определяет тип, отличный от того, что я ожидал. Обычно вам нужно где-то добавить новую привязку let, что действительно мешает и заставляет rustfmt прервать историю отмены. Более того, есть много мест, где определение типа позволит избежать лишней турбо-рыбы.

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

@steveklabnik @ mark-im Блоки try имеют неэргономичные ошибки вывода типов, и приписывание обобщенного типа могло бы быть способом решения этой проблемы, но поскольку приписывание обобщенного типа не является ближайшей функцией или даже наверняка, @joshtriplett (и я согласен) не хочет, чтобы эта функция блокировалась при назначении обобщенного типа.

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


@ rust-lang / lang Я должен признать, что немного сложно понять нюанс ошибки вывода типа из этого потока из-за ограничений GitHub и многих других тем, которые здесь обсуждаются. Поскольку принятие решения о том, как справиться с ошибками логического вывода, - единственное, что блокирует стабилизацию блоков попыток, я думаю, было бы полезно, если бы мы провели встречу для обсуждения этого, и кто-то мог бы взять точку на глубоком понимании проблемы логического вывода. .

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

@withoutboats Я согласен с необходимостью сбора всей информации в одном месте и желанием довести эту функцию до финиша. Тем не менее, я думаю, что в последний раз, когда мы исследовали здесь, также стало ясно, что изменения в Try могут быть трудными из-за обратной совместимости - @cramertj упомянул некоторые конкретные Pin impls, IIRC.

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