Rust: Проблема с отслеживанием трейтов TryFrom/TryInto

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

Проблема отслеживания для https://github.com/rust-lang/rfcs/pull/1542


Сделать:

  • [ ] Удовлетворительна ли существующая документация?
  • [x] https://github.com/rust-lang/rust/pull/56796 Изменить границы на одеяле TryFrom , чтобы использовать Into вместо From
  • [ ] (Ре)стабилизация PR
B-unstable C-tracking-issue T-libs

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

Я хотел бы, чтобы ! стал достаточным словарным типом, чтобы Result<_, !> интуитивно читался как «безошибочный результат» или «результат, который (буквально) никогда не ошибается». Использование псевдонима (не говоря уже об отдельном типе!) кажется мне немного избыточным, и я вижу, что это вызывает, по крайней мере, у меня, мгновенную паузу при чтении подписи типа — «подождите, чем это отличалось от ! снова ?" ЮММВ, конечно.

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

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

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

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

Мы можем вынести его на обсуждение следующего цикла.

🔔 Эта проблема сейчас вступает в цикл окончательного комментирования для стабилизации 🔔

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

У меня есть несколько вопросов о назначении этих черт.

  1. Для каких типов в std они будут реализованы?
  2. Какие типы должны получить такие реализации, как impl TryFrom<T> for T ?
  3. Какие типы должны получить такие реализации, как impl TryFrom<U> for T , если у них уже есть impl From<U> for T ?

cc @sfackler , не могли бы вы расширить текущий набор импликаций, а также некоторые обоснования?

Я думаю, что в целом интуиция должна быть точно такой же для From / Into , за исключением случаев, когда преобразование может завершиться неудачно. Точно так же, как мы постепенно добавляем реализации From и Into к стандартным библиотечным типам, я ожидаю, что мы сделаем то же самое для TryFrom и TryInto . Наиболее важным правилом здесь является то, что преобразование должно быть «канонически очевидным» — если существует более одного разумного способа преобразования одного типа в другой, TryFrom или From могут быть неправильными. вещи для использования.

_В общем_, я не думаю, что следует ожидать, что все типы должны impl TryFrom<T> for T или вручную поднимать impl From<U> for T . В частности, часто неясно, какой тип ошибки выбрать. Однако такие реализации в значительной степени могут и должны быть определены для того, чтобы конкретный API работал. Например, у нас есть оба этих вида реализации для различных комбинаций примитивных целочисленных типов по причинам, изложенным в RFC. В качестве другого примера, один ad-hoc трейт, который заменит TryFrom / TryInto , это postgres::IntoConnectParams , который имеет рефлексивную реализацию для преобразования ConnectParams в себя.

В общем, я не думаю, что следует ожидать, что все типы должны включать TryFrom<T> for T или вручную поднимать импл From<U> for T .

Когда преобразование TryFrom оказывается безошибочным, тип ошибки должен быть enum Void {} , верно? (Или аналогичное перечисление с каким-то другим именем.) Что, кстати, звучит как веская причина для того, чтобы иметь тип общего назначения Void в std .

@SimonSapin , который нарушит API стиля IntoConnectParams , а также вариант использования целочисленного преобразования, описанный в RFC, поскольку типы ошибок безошибочного и ошибочного преобразования не будут совпадать.

@sfackler Нет, если вы используете простые From для типов ошибок (например, try! )! impl<T> From<!> for T может и должен существовать для этой цели.

Точно так же, как мы постепенно добавляем реализации From и Into к стандартным библиотечным типам, я ожидаю, что мы сделаем то же самое для TryFrom и TryInto .

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

impl TryFrom<u32> for char
impl TryFrom<char> for u8
impl TryFrom<Vec<u8>> for String
impl TryFrom<&[u8]> for &str

_В общем_, я не думаю, что следует ожидать, что все типы должны impl TryFrom<T> for T или вручную поднимать impl From<U> for T .

Проблема в том, что если вы хотите использовать TryInto<T> , а T нет в вашем ящике, вам нужно просто надеяться, что impl TryFrom<T> for T реализовано. Это досадное ограничение, которого не существует для From / Into .

@SimonSapin , который нарушит API стиля IntoConnectParams , а также вариант использования целочисленного преобразования, описанный в RFC, поскольку типы ошибок безошибочного и ошибочного преобразования не будут совпадать.

Означает ли это, что тип ошибки каким-то образом фиксируется в зависимости от типа, в который вы конвертируете?

Почему TryFrom::Err и TryInto::Err не ограничены std::error::Error ?

не ограничен std::error::Error?

Это предотвратит превращение Err в () , что является приемлемым типом ошибки для безошибочных преобразований.

Если () является типом ошибки, функция может вернуть Err(()) . Это не делает функцию непогрешимой. Он просто не может предоставить полезную ошибку, когда он терпит неудачу. При написании кода, который использует преобразования, которые могут или не могут быть ошибочными, я думаю, что лучший способ — это ограничиться Into и написать специализацию, которая использует TryInto .

После реализации RFC 1216 ! станет подходящим типом ошибки для безошибочных преобразований. Я _думаю_, это будет означать, что Error , связанный с TryFrom::Err / TryInto::Err , будет в порядке.

Мне бы, конечно, понравилось, если бы реализация From<T> дала реализацию TryFrom<T, Err = !> (и то же самое для Into , конечно).

Я _думаю_, это будет означать, что Error , связанный с TryFrom::Err / TryInto::Err , будет в порядке.

Да, у нас точно будет impl Error for ! { ... } именно для таких случаев.

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

В моем POV все связанные типы, представляющие ошибки, должны быть ограничены Error . К сожалению, один тип мы уже оставили без него: FromStr::Err (единственные общедоступные типы — это TryInto и TryFrom , проверенные с помощью ack 'type Err;' и ack 'type Error;' ). Пусть больше так не будет.

Недавно обсуждалось, что команда libs решила сделать ставку на стабилизацию этих трейтов, пока не будет разработан тип ! .

Я предполагаю, что теперь это больше не заблокировано, верно?

Удаление номинации как @sfackler будет исследовать типы ! и эти черты.

Будет ли это изменение, чтобы стабилизироваться в обозримом будущем?

Почему TryFrom::Err и TryInto::Err не ограничены std::error::Error?

std::err::Error не существует в сборках #![no_std] . Кроме того, во многих случаях нет никакой пользы от реализации std::err::Error для типа ошибки, даже если он доступен. Таким образом, я бы предпочел, чтобы такой границы не было.

Я экспериментировал с реализацией этого в своей собственной библиотеке. Я хотел бы повторить озабоченность, выраженную @SimonSapin в https://github.com/rust-lang/rfcs/pull/1542#issuecomment -206804137: try!(x.try_into()); сбивает с толку, потому что слово «попытаться» использовал два разных способа в одном и том же заявлении.

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

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

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

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

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

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

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

Я хотел бы повторить беспокойство, выраженное @SimonSapin в rust-lang/rfcs#1542 (комментарий)

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

Кроме того, я думаю, что стабилизация раньше, чем позже, важнее, чем еще один раунд именных велосипедов. Прошли месяцы с тех пор, как RFC был принят, и это было реализовано #[unstable] .

Использование try_ для разновидностей функций, возвращающих Result, уже стало полустандартом.

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

Кроме того, я не нашел никакого практического преимущества в написании этого:

impl TryInto<X> for Y {
    type Err = MyErrorType;

   fn try_into(self) -> Result<X, Self::Err> { ... }
}

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

    fn into_x(self) -> Result<X, MyErrorType> { ... }

Мне никогда не приходилось писать общий код, параметризованный TryInto или TryFrom , несмотря на множество преобразований, поэтому последней формы достаточно для всех моих применений в типах, которые я определяю. Я думаю, что наличие параметров TryInto<...> или TryFrom<...> кажется сомнительной формой.

Кроме того, я обнаружил, что присвоение имени связанному типу ошибки Err вместо Error было подвержено ошибкам, поскольку я всегда вводил Error . Я заметил, что эта ошибка была допущена еще во время составления самого RFC: https://github.com/rust-lang/rfcs/pull/1542#r60139383. Кроме того, код, который использует Result , уже широко использует имя Err , поскольку это конструктор Result .

Было альтернативное предложение, в котором особое внимание уделялось целочисленным типам и использовалась такая терминология, как «расширить» и «сузить», например, x = try!(x.narrow()); , которое я также реализовал. Я обнаружил, что этого предложения достаточно для моего использования предложенных здесь функций в моем реальном использовании, поскольку в итоге я выполнял такие преобразования только для встроенных целочисленных типов. Он также более эргономичен и понятен (IMO) для случаев использования, для которых его достаточно.

Кроме того, я не нашел никакой практической пользы от написания этого...

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

impl TryInto<TcpStream> for SocketAddr {
    type Err = io::Error;
    fn try_into(self) -> Result<TcpStream, io::Error> {
        TcpStream::connect(self)
    }
}

impl<T> TryInto<MutexGuard<T>> for Mutex<T> {
    type Err = TryLockError<MutexGuard<T>>;
    fn try_into(self) -> Result<Mutex<T>, Self::Err> {
        self.try_lock()
    }
}

impl TryInto<process::Output> for process::Child {
    type Err = io::Error;
    fn try_into(self) -> Result<process::Output, io::Error> {
        self.wait_with_output()
    }
}

impl TryInto<String> for Vec<u8> {
    type Err = FromUtf8Error;
    fn try_into(self) -> Result<String, FromUtf8Error> {
        String::from_utf8(self)
    }
}

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

Я думаю, что наличие параметров TryInto<...> или TryFrom<...> кажется сомнительной формой.

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

std::err::Error не существует в сборках #![no_std]. Кроме того, во многих случаях нет смысла реализовывать std::err::Error для типа ошибки, даже если он доступен. Таким образом, я бы предпочел, чтобы не было такой границы.

Единственным преимуществом ограничения std::error::Error является то, что оно может быть возвращаемым значением cause() другой ошибки. Я действительно не знаю, почему нет core::error::Error , но я не изучал это.

Кроме того, я обнаружил, что присвоение имени связанному типу ошибки Err вместо Error чревато ошибками, поскольку я всегда вводил Error. Я заметил, что эта ошибка была допущена еще при написании самого RFC: rust-lang/rfcs#1542 (комментарий). Кроме того, код, который использует Result, уже широко использует имя Err, поскольку это конструктор Result.

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

Независимо от того, являются ли TryFrom и TryInto слишком общими, я действительно хотел бы видеть ошибочные преобразования, по крайней мере, между целочисленными типами, в стандартной библиотеке. У меня есть ящик для этого, но я думаю, что варианты использования заходят достаточно далеко, чтобы сделать его стандартным. Когда Rust был альфа или бета, я помню, как использовал FromPrimitive и ToPrimitive для этой цели, но у этих трейтов были еще большие проблемы.

Error нельзя переместить в core из-за проблем с когерентностью.

Также из-за проблем с когерентностью Box<Error> не реализует Error , что является еще одной причиной, по которой мы не связываем тип Err .

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

where T: TryInto<Foo>, T::Err: Error

Я обычно не комментирую эти темы, но я ждал этого некоторое время, и, как было сказано несколькими выше, я не уверен, в чем задержка; Мне всегда нужна черта from, которая может потерпеть неудачу. У меня есть весь код, который называется try_from ... Эта черта _идеально_ воплощает эту идею. Пожалуйста, позвольте мне использовать его на стабильной ржавчине.

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

При этом я считаю, что str::parse сильно выиграет от этого, поскольку он также специализируется на признаке FromStr в качестве привязки, что точно ( TryFrom<str> ) специализировано вручную.

Так что поправьте меня, если я ошибаюсь, но я считаю, что стабилизация и использование этого для str::parse :

  1. удалить повторную реализацию шаблона и, следовательно, удалить менее знакомый и специализированный признак
  2. добавьте TryFrom<str> в подпись, которая правильно находится в модуле convert и более очевидно, что она делает
  3. позволит вышестоящим клиентам реализовывать TryFrom<str> для своих типов данных и бесплатно получать str.parse::<YourSweetDataType>() вместе с другими try_from , которые они хотят реализовать, что улучшает логическую организацию кода.

Наконец, абстракции в сторону, повторное использование кода, бла-бла, я думаю, что одно из заниженных преимуществ подобных трейтов — это семантическая покупка, которую они предоставляют как новичкам, так и ветеранам. По сути, они обеспечивают (или начинают обеспечивать, чем больше мы стабилизируем) единый ландшафт со знакомым, канонизированным поведением. Default , From , Clone действительно отличные примеры этого. Они обеспечивают запоминающийся ландшафт функций, к которому пользователи могут обращаться при выполнении определенных операций, и чье поведение и семантику они уже хорошо понимают (выучи один раз, применяй везде). Например:

  1. Я хочу версию по умолчанию; о, позволь мне дотянуться до SomeType::default()
  2. Я хочу преобразовать это, интересно, реализован ли SomeType::from(other) (вы можете просто ввести его и посмотреть, скомпилируется ли он, не обращаясь к документации)
  3. Я хочу клонировать это, clone()
  4. Я хочу попробовать получить вот это, а так как ошибки - неотъемлемая часть ржавчины, то в сигнатуре может глючить, так что позвольте мне try_from - ох, подождите :P

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

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

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

И это все, что я могу сказать по этому поводу ;)

Ранее это было заблокировано при расследовании возможности использования ! в качестве типа ошибки для безошибочных целочисленных преобразований. Поскольку функция реализована в настоящее время, это не работает даже в самых простых случаях: https://is.gd/Ws3K7V.

Мы все еще думаем об изменении имен методов, или мы должны добавить эту функцию в FCP?

@sfackler Эта ссылка на игровую площадку работает для меня, если я изменю тип возвращаемого значения в строке 29 с Result<u32, ()> на Result<u32, !> : https://is.gd/A9pWbU Он не распознает, что let Ok(x) = val; является неопровержимым шаблоном, когда val имеет тип Err!, но это не похоже на проблему блокировки.

@Ixrec Основной мотивацией для этих признаков было преобразование в целочисленные определения типов C и обратно. Если у меня есть функция

fn foo(x: i64) -> Result<c_long, TryFromIntError> {
    x.try_into()
}

Это будет компилироваться на целях i686, но не на целях x86_64.

Точно так же, скажем, я хочу заменить тип IntoConnectParams : https://docs.rs/postgres/0.13.4/postgres/params/trait.IntoConnectParams.html. Как я могу это сделать, если есть одеяло impl<T> TryFrom<T> for T { type Error = ! } ? Мне нужна рефлексивная реализация для ConnectParams , но с конкретным типом ошибки, отличным от ! .

Он не может распознать, что let Ok(x) = val; является неопровержимым шаблоном, когда val имеет тип Err!

Обратите внимание, что для этого открыт PR .

Если у меня есть функция...

Это должно работать, хотя

fn foo(x: i64) -> Result<c_long, TryFromIntError> {
    let val = x.try_into()?;
    Ok(val)
}

Рискуя быть раздражающим комментарием +1, я просто хочу упомянуть, что после того, как макросы 1.1 появятся в Rust 1.15, try_from станет последней функцией, удерживающей Ruma на ночном Rust. Ожидается стабильная версия try_from!

На более существенной ноте...

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

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

Кроме того, я обнаружил, что присвоение имени связанному типу ошибки Err вместо Error чревато ошибками, поскольку я всегда вводил Error. Я заметил, что эта ошибка была допущена еще при составлении самого RFC: rust-lang/rfcs#1542 (комментарий). Кроме того, код, использующий Result, уже широко использует имя Err, поскольку он является конструктором Result.

Я согласен с этим. Большинство других типов ошибок, с которыми я сталкивался в библиотеках, называются «Error», и мне нравится, что до сих пор «Err» означало только Result::Err . Кажется, что стабилизация его как «Err» приведет (без каламбура) к тому, что люди будут постоянно ошибаться в имени.

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

Я думаю, что выбрал Err для согласованности с FromStr , но я был бы очень рад переключиться на Error перед стабилизацией!

последняя фича, удерживающая Ruma на nightly Rust

Точно так же серверной части альтернативной игровой площадки требуется только ночной доступ к TryFrom ; ночные serde были слишком нестабильны, на мой вкус, поэтому я перешел к настройке скрипта сборки. С 1.15 я вернусь к #[derive] . Я с нетерпением жду, когда эта функция станет стабильной!

@sfackler Извините, я не следил. В случае целочисленных преобразований кажется, что нам действительно нужно не определять тип c_ulong либо в u32 , либо u64 в зависимости от платформы, а каким-то образом сделать его новым типом. Таким образом, реализация TryFrom<c_ulong> не может мешать реализации TryFrom<u{32,64}> .
В конце концов, у нас всегда будут проблемы «компилирует одну платформу, но не другую», если мы определяем типы по-разному на разных платформах. Обидно, что приходится жертвовать совершенно логичной импликацией TryFrom<T> for U where U: From<T> только для того, чтобы мы могли поддерживать то, что кажется сомнительной практикой.

Я серьезно не предлагаю блокировать этот RFC до тех пор, пока мы не получим целочисленный RFC нового типа, написанный+объединенный+стабилизированный. Но мы должны помнить об этом на будущее.

Точно так же, скажем, я хочу заменить тип IntoConnectParams:

Хотя в чем здесь проблема? Почему бы вам не использовать один тип ошибки для TryFrom<ConnectParams> и другой для TryFrom<&'a str> ?

Я бы не стал выступать за то, чтобы мы ломали буквально весь код FFI в мире. Попытки найти похожие целочисленные обертки newtype, такие как Wrapping , но не увенчались успехом, влекут за собой огромные затраты на эргономику.

Есть ли impl<T> From<!> for T в стандартной библиотеке? Я не вижу этого в документах, но это может быть просто ошибка rustdoc. Если его там нет, то нет никакого способа преобразовать импл TryFrom<ConnectParams> ! Error в тот, который мне действительно нужен.

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

Я думал о чем-то большем, чем возможность определять свои собственные целочисленные типы a. ля. С++:

trait IntLiteral: Integer {
    const SUFFIX: &'static str;
    const fn from_bytes(is_negative: bool, bytes: &[u8]) -> Option<Self>; // or whatever
}

impl IntLiteral for c_ulong {
    const SUFFIX: &'static str = "c_ulong";
    ...
}

extern fn foo(x: c_ulong);

foo(123c_ulong); // use a c_ulong literal
foo(123); // infer the type of the integer

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

Есть ли impl<T> From<!> for T в стандартной библиотеке?

В настоящее время нет, потому что это противоречит реализации From<T> for T . Насколько я понимаю, специализация impl в конечном итоге должна справиться с этим.

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

Каковы сроки стабилизации специализации и ! ?

По специализации не знаю. Для самого ! это в основном вопрос того, когда я могу объединить свои патчи.

@canndrew Я определенно согласен с тем, что это не должно быть реализовано для всего. В документах говорится _попытаться создать Self через преобразование_, но что считается преобразованием? Как насчет …_изменить одно и то же из одного представления на другое или добавить или удалить оболочку_? Это касается ваших Vec<u8> -> String и Mutex<T> -> MutexGuard<T> , а также таких вещей, как u32 -> char и &str -> i64 ; исключая SocketAddr -> TcpStream и process::Child -> process::Output .

Я чувствую, что impl From<T> for U , вероятно, должно означать TryFrom<T, Err=!> for U . Без этого функции, которые принимают TryFrom<T> s, не могут также принимать From<T> s. (Использование try_from() | try_into() для конкретного, безошибочного преобразования, вероятно, было бы просто анти-шаблоном. Вы _могли бы_ сделать это, но... это было бы глупо, так что просто не делайте этого. .)

Я чувствую, что имплдля U, вероятно, следует подразумевать TryFromдля тебя.

Согласованный.

Вы могли бы это сделать, но… это было бы глупо, так что просто не надо.

Звучит как потенциальный отрывистый ворс.

@BlacklightShining Я думаю, что это должно быть реализовано для типов, где, учитывая тип вывода, «преобразование» очевидно. Как только возможны множественные преобразования (utf8/16/32? сериализация или кастинг? и т. д.), этого следует избегать.

ПО МОЕМУ МНЕНИЮ:

Прямо сейчас мы могли бы легко предоставить полную реализацию TryFrom<&str> для всего, что реализует FromStr :

impl<'a, T: FromStr> TryFrom<&'a str> for T {
    type Err = <T as FromStr>::Err;
    fn try_from(s: &'a s) -> Result<T, Self::Err> {
        T::from_str(s)
    }
}

Я бы хотел, чтобы что-то подобное было добавлено до того, как try_from будет стабилизировано, или хотя бы какая-то ссылка на него в документах. В противном случае может возникнуть путаница при наличии разных реализаций TryFrom<&'a str> и FromStr .

Я согласен, что это было бы хорошим включением, но оно может конфликтовать с предложенным методом Try -> TryFrom impl?

@sfackler потенциально. Было бы совершенно нормально, если бы TryFrom , TryInto и FromStr ссылались друг на друга в документах, но меня больше всего беспокоит то, что не существует стандарта, в котором говорится, что str::parse и str::try_into возвращают одно и то же значение.

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

Например, предположим, что кто-то создает структуру Password для веб-сайта. Они могут предположить, что "password".parse() проверит пароль на достоверность, а затем преобразует его в хэш, тогда как Password::try_from("1234abcd") может предположить, что "1234abcd" уже является хэшем, хранящимся в базе данных, и попытаться чтобы разобрать его непосредственно в хэш, который можно сравнить.

Это имеет смысл, учитывая, что формулировка parse подразумевает выполнение определенного уровня синтаксического анализа, тогда как try_from — это просто преобразование типов. Однако на самом деле мы могли бы уточнить, что обе эти функции предназначены для выполнения одного и того же.

Несмотря на то, что языковая команда предложила закрыть RFC за устаревшие анонимные параметры , все они, кажется, согласны с тем, что в идеале мы должны прекратить создавать новый код, использующий анонимные параметры. Имея это в виду, можем ли мы обновить подпись try_from / try_into , чтобы указать имена параметров? Или было бы важнее сохранить симметрию с from / into ?

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

@jimmycuadra , конечно, да! Хотите отправить PR, добавив имена некоторых параметров?

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

impl<T, U> TryFrom<U> for T
    where T: From<U>
{
    type Error = !;

    fn try_from(u: U) -> Result<T, !> {
        Ok(T::from(u))
    }
}

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

введите Ошибка = !;

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

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

Я придерживаюсь мнения, что TryFrom<str> и FromStr должны быть функционально идентичны, и в документации должно быть ясно, что их реализации должны быть идентичными. Реализация одного должна также дать вам другой, по крайней мере, с точки зрения разрешения использовать str::parse . Если бы в Rust с самого начала было TryFrom , FromStr никогда бы не понадобилось. По этой причине я бы также задокументировал TryFrom<str> как предпочтительную форму для нового кода.

@jimmycuadra , в этом случае мы должны изменить parse , чтобы использовать TryFrom , а затем добавить одеяло для FromStr -> TryFrom .

Если мы собираемся изменить str::parse для реализации в терминах TryFrom , должны ли мы аналогичным образом изменить другие реализации FromStr для конкретных типов (т.е. все реализации в этом списке : https://doc.rust-lang.org/stable/std/str/trait.FromStr.html)? Должны ли быть обновлены документы для FromStr , чтобы вместо них предлагалось использовать TryFrom ? Должны ли текущие конкретные реализации FromStr переместить свои документы в версию TryFrom ?

Я думаю, для обратной совместимости мы не можем изменить FromStr , верно?

Удаление FromStr в пользу TryFrom<&str> – это определенно то, о чем следует помнить в Rust 2.0.

Да, как только эта функция стабилизируется, мы зарегистрируем проблему и пометим ее как 2.0-breakage-wishlist.

Еще одна вещь, которую следует учитывать, — это добавление метода parse_into к String , который использует TryFrom<String> . Я обнаружил, что реализую TryFrom для обоих часто, если тип внутренне хранит String , но все еще требует проверки.

Если мы собираемся оставить реализациюTryFrom для T, где T: From для будущего RFC, готова ли эта функция к стабилизации сейчас? Я действительно не хочу пропустить еще один цикл выпуска, поэтому я надеюсь, что у некоторых людей из команды Rust хватит возможностей обсудить и принять решение.

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

Я ожидаю, что уже наличие T : From<U> поместит U : TryFrom<T> в категорию изменений « очевидная дыра API », когда реализация будет разумной.

Это означает, что должно быть по крайней мере T : TryFrom<T> с Error = ! , но версия для любого непогрешимого From явно лучше, чем эта.

IMO на самом деле нет четкого различия между тем, должен ли From обеспечивать реализацию TryFrom или должен ли TryFrom обеспечивать реализацию From .

Потому что, с одной стороны, вы могли бы считать T::from(val) всего лишь T::try_from(val).unwrap() , а с другой стороны, вы могли бы считать T::try_from(val) всего лишь Ok(T::from(val)) . Что лучше? Я не знаю.

вы можете считать T::from(val) просто T::try_from(val).unwrap()

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

@clarcharr Поскольку From не должен паниковать, варианты From в пересчете на TryFrom<Error=!> или наоборот. Но я бы не хотел, чтобы обычный совет был "Вы должны реализовать TryFrom с type Error = ! " вместо "Вы должны реализовать From ".

Любой способ получить какое-то движение по стабилизации этого? У нас мало времени до выхода бета-версии 1.18. @сфаклер?

@rfcbot fcp слияние

Член команды @sfackler предложил объединить это. Следующий шаг — просмотр остальными отмеченными командами:

  • [x] @BuntSushi
  • [x] @Кимунди
  • [x] @alexcrichton
  • [x] @атурон
  • [х] @brson
  • [x] @sfackler

В настоящее время в списке нет проблем.

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

См. этот документ для получения информации о том, какие команды могут давать мне члены команды, отмеченные тегами.

@sfackler : просто чтобы проверить, хорошо ли мы разбираемся в различных проблемах, связанных с ! и общими импликациями? Я знаю, что мы говорили об этом на собрании libs, но было бы полезно получить резюме здесь.

@aturon Последней дискуссией по этому поводу был @sfackler , который задавался вопросом , должна ли impl From<T> for U предоставить impl TryFrom<T> for U где TryFrom::Error = ! .

@briansmith предложил принять решение об этом в отдельном RFC после того, как будут проработаны нерешенные вопросы, связанные с типом never.

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

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

  1. TryFrom предназначен для ошибочных конверсий _только_, поэтому в нем нет таких элементов, как u8 -> u128 или usize -> usize .
  2. TryFrom предназначен для _всех_ преобразований, некоторые из которых безошибочны и поэтому имеют необитаемый тип TryFrom::Error .

Но сейчас все находится в странном гибридном состоянии, когда компилятор вставляет код проверки для преобразования i32 -> i32 , а вы не можете выполнить преобразование String -> String .

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

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

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

Если есть пример случая, когда вы когда-нибудь использовали бы try_from вместо from для безошибочного преобразования, я, безусловно, мог бы изменить свое мнение.

Какие возражения! как тип ошибки?

До стабилизации ! осталось несколько месяцев. Выберите один из вариантов стабилизации TryFrom в ближайшем будущем или получения impl<T, U> TryFrom<U> for T where T: From<U> .

Рассматривали ли мы здесь использование псевдонимов трейтов (rust-lang/rfcs#1733)? Когда это произойдет, мы можем связать From<T> TryFrom<T, Error=!> , сделав два трейта одним и тем же.

@lfairy Это, увы, сломало бы пользователей impl s из From .

@glaebhoerl Да, вы правы 😥 В разделе мотивации этого RFC упоминаются псевдонимы impl s, но фактическое предложение запрещает их.

(Даже если это не так, методы имеют разные имена и т.д.)

Это может попасть в список желаний 2.0, но, тем не менее, это не произойдет, ничего не сломав.

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

Выберите один из вариантов стабилизации TryFrom в ближайшем будущем или получения impl<T, U> TryFrom<U> for T where T: From<U> .

Я думаю, что основной вопрос здесь заключается в том, относятся ли безошибочные преобразования к этой черте. Я думаю, что да, для таких вещей, как безошибочное преобразование в RFC и для общего использования (аналогично тому, как есть кажущиеся бесполезными T:From<T> ). Учитывая это, я больше всего хочу избежать мира, в котором ожидается, что каждый реализатор типа будет impl TryFrom<MyType> for MyType , а каждый импл From также должен привести к реализации TryFrom . (Или получить сообщения об ошибках позже за то, что они не были предоставлены.)

Итак, можем ли мы реализовать одеяло без стабилизации ! ? Я думаю, что есть способ, так как у нас уже есть ! -подобные типы в библиотеке, такие как std::string::ParseError . (" Это перечисление немного неудобно: на самом деле оно никогда не будет существовать. ")

Набросок того, как это может работать:

  • Новый тип, core::convert::Infallible , реализован точно так же, как std::string::ParseError . (Возможно, даже изменить последний на псевдоним типа для первого.)
  • impl<T> From<Infallible> for T , чтобы он был совместим в ? с любым типом ошибки (см. материал c_foo позже)
  • Используйте Infallible в качестве типа Error в реализации одеяла.
  • Позже рассмотрите type Infallible = !; как часть стабилизации типа never.

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

Что касается c_foo : приведенное выше по-прежнему позволяет использовать такой код:

fn foo(x: c_int) -> Result<i32, TryFromIntError> { Ok(x.try_into()?) }

Но это сделало бы такой код переносимым «футганом» из-за разных типов ошибок.

fn foo(x: c_int) -> Result<i32, TryFromIntError> { x.try_into() }

Лично меня эта разница не беспокоит, потому что пока c_int является псевдонимом типа, есть «полностью автоматический» футган:

fn foo(x: c_int) -> i32 { x }

И в целом, код, ожидающий, что связанный тип черты будет одинаковым для разных импликаций, мне кажется запахом кода. Я прочитал TryFrom как «обобщить From »; если бы целью было «лучшее преобразование между целочисленными подмножествами» — что также кажется полезным — тогда «всегда один и тот же тип ошибки» логичен, но вместо этого я ожидал бы что-то нацеленное на std::num , вероятно, как num::cast::NumCast (или boost::numeric_cast ).

(Кроме того: с #[repr(transparent)] в слиянии FCP, возможно, типы c_foo могут стать новыми типами, и в этот момент эти преобразования могут быть более последовательными. Импликации From&TryFrom могут кодифицировать C "char <= short < = int <= long", а также требуемые по стандарту минимальные размеры для них, такие как c_int:From<i16> или c_long:TryFrom<i64> . Тогда приведенное выше преобразование будет i32:TryFrom<c_int> для всех платформы, всегда с одним и тем же типом Error , и проблема исчезает.)

Что касается «Это перечисление немного неудобно: оно никогда не будет существовать».

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

@sunjay () - это системное представление типа «это может случиться, но вам нечего сказать, когда это произойдет». Необитаемые типы (такие как ! и std::string::ParseError ) противоположны тому, как система типов говорит: «Эта ситуация не может произойти никогда, поэтому вам не нужно иметь с ней дело».

@джиммикуадра

Если есть пример случая, когда вы когда-нибудь использовали бы try_from вместо from для безошибочного преобразования, я бы, конечно, передумал.

@scottmcm

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

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

let cfg = config![
    BoolOpt::SomeCfgKey => true,
    "SomeOtherCfgKey" => 77,
];

Короче говоря, макрос расширяется до списка вызовов ($k, $v).into() . Я хотел бы проверить преобразование строковых ключей, чтобы убедиться, что они называют допустимый параметр конфигурации, т.е. реализация TryFrom<(String, ???)> и изменение макроса для использования ($k, $v).try_into() . Было бы сложнее сделать все это, если бы не было единого имени метода для макроса, который можно было бы использовать для всех преобразований.

:bell: Это сейчас вступает в свой последний период комментариев , в соответствии с обзором выше . :колокол:

Мне на самом деле очень нравится идея:

impl<U: TryFrom<T, Error=!>> From<T> for U {
    fn from(val: T) -> U {
        val.unwrap()
    }
}

Потому что любой, кто хочет TryFrom<Error=!> , может реализовать его, но люди все равно могут реализовать From , если захотят. Возможно, мы могли бы в конечном итоге отказаться From , но мы не обязаны.

План @scottmcm по использованию пустого перечисления мне кажется отличным.

@Ericson2314 вы писали :

Нет, если вы используете простые From для типов ошибок (например, try! )! impl<T> From<!> for T может и должен существовать для этой цели.

Как это будет работать на практике? Скажем, я пытаюсь написать такую ​​функцию:

fn myfn<P: TryInto<MyType>>(p: P) -> Result<(), MyError>

За исключением того, что это, конечно, не работает, мне нужно указать Error= на TryInto . Но какой тип я должен писать там? MyError кажется очевидным, но тогда я не могу использовать MyType с одеялом TryFrom .

Вы предлагаете следующее?

fn myfn<E: Into<MyError>, P: TryInto<MyType, Error=E>>(p: P) -> Result<(), MyError>

Это кажется довольно многословным.

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

Возможно, определение TryFrom следует изменить на следующее:

pub trait TryFrom<T, E>: Sized {
    type Error: Into<E>;

    fn try_from(t: T) -> Result<Self, E>;
}

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

fn myfn<P: TryInto<MyType>>(p: P) -> Result<(), MyError> where MyError: From<P::Error>

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

РЕДАКТИРОВАТЬ: И попытка с таким вариантом, как P::Error: Into<MyError> , на самом деле не работает с ? , поскольку нет общей реализации From для Into . Изменив TryFrom , как вы показали, я ожидаю, что столкнусь с той же проблемой.

Последний период комментариев завершен.

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

В конкретном случае ! у нас должна быть общая реализация, но я помню, что она перекрывает другую. Это раздражающее совпадение, поскольку обе реализации согласны с реализацией --- неопределенное поведение/мертвый код.

Комментарий к этому из другого выпуска: https://github.com/rust-lang/rust/pull/41904#issuecomment -300908910

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

Команда libs снова говорила об этом пару недель назад (извините за задержку с записью)! Мы пришли к выводу, что источником проблем с этой функцией было, прежде всего, то, что случай FFI на самом деле не подходит ни к одному другому варианту использования этих признаков — он уникален тем, что вы вызываете его для конкретных типов через псевдонимы, которые различаются. на основе цели.

Таким образом, основной план действий состоит в том, чтобы добавить impl<T, U> TryFrom<T> for U where U: From<T> и удалить явные импликации для безошибочных целочисленных преобразований. Для обработки варианта использования FFI мы создадим отдельный API.

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

Я открыл PR для «отдельного API» для интегральных типов: https://github.com/rust-lang/rust/pull/42456

Мне никогда не приходилось писать общий код, параметризованный TryInto или TryFrom , несмотря на множество преобразований, поэтому последней формы достаточно для всех моих применений в типах, которые я определяю. Я думаю, что наличие параметров TryInto<...> или TryFrom<...> кажется сомнительной формой.

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

Пожалуйста, не удаляйте это.

Мне никогда не приходилось писать общий код, параметризованный с помощью TryInto или TryFrom.

Даже если это так, я не думаю, что это делает TryInto и TryFrom намного менее полезными. Я везде использую Into и From в нестандартных контекстах. Добавление impl трейтов из стандартной библиотеки выглядит гораздо более «нормальным» и «ожидаемым», чем набор специальных встроенных методов преобразования.

Мне никогда не приходилось писать общий код, параметризованный с помощью TryInto или TryFrom.

Из одного из моих проектов:

pub fn put_str_lossy<C, S> (&self, s: S)
    where C: TryInto<ascii::Char>,
          S: IntoIterator<Item = C>
{
    for c in s.into_iter() {
        self.put_char(match c.try_into() {
            Ok(c) => c,
            Err(_) => ascii::QUESTION_MARK,
        });
    }
}

Ожидается ли, что реализации этих трейтов будут подчиняться каким-то особым законам? Например, если мы можем преобразовать A в B и B в A, требуется ли, чтобы преобразование было обратимым в случае успеха?:

#![feature(try_from)]

use std::convert::{TryFrom, TryInto};

fn invertible<'a, A, B, E>(a: &'a A) -> Result<(), E>
    where A: 'a + TryFrom<&'a B>,
          A: PartialEq,
          B: 'a + TryFrom<&'a A>,
          E: From<<A as TryFrom<&'a B>>::Error>,
          E: From<<B as TryFrom<&'a A>>::Error>,
{
    let b = B::try_from(a)?;
    let a2 = A::try_from(&b)?;
    assert!(a == &a2);
    Ok(())
}

редактировать: с/рефлексивный/обратимый/

@briansmith Учитывая, как работает From , я бы сказал, что это необратимо.

use std::collections::BinaryHeap;

fn main() {
    let a = vec![1, 2];
    let b = BinaryHeap::from(a.clone());
    let c = Vec::from(b);
    assert_ne!(a, c);
}

Поэтому мне интересно узнать о текущих реализациях этой черты. Как было показано в #43127 (см. также #43064), я не знаю, связано ли это исключительно с тем, что реализация использует i/u128, но похоже, что вызовы TryInto не встроены. Это не очень оптимально и не совсем в духе абстракций с нулевой стоимостью. Например, использование <u32>::try_into<u64>() должно оптимизироваться до простого расширения знака в окончательной сборке (при условии, что платформа 64-битная), но вместо этого это приводит к вызову функции.

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

В соответствии с #42456 у нас, вероятно, не будет impl TryFrom непосредственно для целочисленных типов, но то, как будет выглядеть черта "NumCast" (на которую должен переключиться #43127), все еще разрабатывается.

Независимо от того, перейдут ли они в конечном итоге к другому признаку, эти преобразования сегодня реализованы в libcore и могут использоваться в libcore. Я думаю, что использование u128 / i128 для всех целочисленных преобразований было сделано для простоты исходного кода. Вместо этого нам, вероятно, следует иметь другой код в зависимости от того, является ли исходный тип более широким или более узким, чем целевой. (Возможно, с разными вызовами макросов на основе #[cfg(target_pointer_width = "64")] против #[cfg(target_pointer_width = "32")] против #[cfg(target_pointer_width = "16")] .)

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

Я не знал, что команда libs согласилась с тем, что мы можем использовать идею @scottmcm в качестве обходного пути для нестабильного типа взрыва. Я могу работать над PR, чтобы внести изменения, упомянутые @sfackler, используя обходной путь.

Круто, спасибо @jimmycuadra!

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

Есть и другие удобные преобразования, которые выиграют от этих признаков, например, TryFrom<&[T]> для &[T; N] . Недавно я представил PR для реализации именно этого: https://github.com/rust-lang/rust/pull/44764.

Подобные преобразования достаточно важны для меня, чтобы TryFrom стабилизировались.

После слияния # 44174 я считаю, что теперь это разблокировано.

Этот PR удалил автоматическую реализацию FromStr для любого типа, который реализует TryFrom<&str> , потому что система типов в настоящее время не может поддерживать это, даже со специализацией. Намерение состоит в том, чтобы FromStr и parse устарели в пользу TryFrom<&str> и try_into , как только эта функция будет стабилизирована. Прискорбно потерять временную совместимость между ними — если у кого-то есть идеи временного решения, пожалуйста, высказывайтесь.

Если изменений больше не требуется и кто-то из команды libs дает зеленый свет для стабилизации, я могу сделать стабилизационный PR и PR, чтобы исключить FromStr / parse .

и PR, чтобы объявить FromStr/parse устаревшим.

Предупреждения об устаревании не следует добавлять в Nightly до тех пор, пока замена не будет доступна в Stable (или пока https://github.com/rust-lang/rust/issues/30785 не будет реализовано), чтобы можно было в любой момент сделать crate build без предупреждений на всех трех каналах выпуска.

Я пропустил другой PR, так как ссылки не приводят к уведомлениям по электронной почте. Я заметил, что есть определенный impl From<Infallible> for TryFromIntError . Разве это не должно быть impl<T> From<Infallible> for T , как обсуждалось?

@jethrogb К сожалению, это конфликтует с impl<T> From<T> for T , поэтому это невозможно (пока мы не получим пересечение? - и использование ! здесь тоже не работает).

@scottmcm а, конечно.

Я не думаю, что вам нужны пересечения? Разве это не прямая специализация?

Я не читал другие комментарии, но TryFrom у меня сейчас не работает (раньше работало нормально).

версия ржавчины:

rustc 1.22.0-nightly (d6d711dd8 2017-10-10)
binary: rustc
commit-hash: d6d711dd8f7ad5885294b8e1f0009a23dc1f8b1f
commit-date: 2017-10-10
host: x86_64-unknown-linux-gnu
release: 1.22.0-nightly
LLVM version: 4.0

Соответствующий раздел кода, на который жалуется ржавчина, находится здесь: https://github.com/fschutt/printpdf/blob/master/src/types/plugins/graphics/two_directional/image.rs#L29 -L39 и https://github. .com/fschutt/printpdf/blob/master/src/types/plugins/graphics/xobject.rs#L170 -L200 — несколько недель назад он скомпилировался нормально, поэтому библиотека до сих пор имеет значок «прохождение сборки».

Однако в последней ночной сборке TryFrom , кажется, ломается:

error[E0119]: conflicting implementations of trait `std::convert::TryFrom<_>` for type `types::plugins::graphics::two_dimensional::image::Image`:
  --> src/types/plugins/graphics/two_dimensional/image.rs:29:1
   |
29 | / impl<T: ImageDecoder> TryFrom<T> for Image {
30 | |     type Error = image::ImageError;
31 | |     fn try_from(image: T)
32 | |     -> std::result::Result<Self, Self::Error>
...  |
38 | |     }
39 | | }
   | |_^
   |
   = note: conflicting implementation in crate `core`

error[E0119]: conflicting implementations of trait `std::convert::TryFrom<_>` for type `types::plugins::graphics::xobject::ImageXObject`:
   --> src/types/plugins/graphics/xobject.rs:170:1
    |
170 | / impl<T: image::ImageDecoder> TryFrom<T> for ImageXObject {
171 | |     type Error = image::ImageError;
172 | |     fn try_from(mut image: T)
173 | |     -> std::result::Result<Self, Self::Error>
...   |
199 | |     }
200 | | }
    | |_^
    |
    = note: conflicting implementation in crate `core`

error: aborting due to 2 previous errors

error: Could not compile `printpdf`.

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

@fschutt Конфликтующий импл, вероятно, impl<T, U> TryFrom<T> for U where U: From<T> , добавленный в https://github.com/rust-lang/rust/pull/44174. Может существовать T , такой как T: ImageDecoder и Image: From<T> .

Есть ли что-то, что еще нужно для этого, чтобы выйти из ворот функции?

Если https://github.com/rust-lang/rust/issues/35121 сначала стабилизируется, мы можем удалить тип Infallible , введенный в https://github.com/rust-lang/rust/pull/ . ! . Я не знаю, считается ли это требованием.

Я думаю, что основным блокировщиком здесь по-прежнему являются целые типы. Либо мы используем отдельный трейт Cast для целочисленных типов https://github.com/rust-lang/rust/pull/42456#issuecomment -326159595, либо сначала делаем переносимость lint #41619.

Итак, у меня было перечисление, которое реализовывало TryFrom для AsRef<str> , но это сломалось несколько месяцев назад. Я думал, что это ошибка, появившаяся в nightly, которая со временем исчезнет, ​​но, похоже, это не так. Этот шаблон больше не поддерживается для TryFrom ?

impl<S: AsRef<str>> TryFrom<S> for MyEnum {
    type Error = &'static str;

    fn try_from(string: S) -> Result<Self, Self::Error> {
        // Impl here
    }
}
...

Какие еще есть варианты конвертации из &str и String ?

@kybishop реализует ли MyEnum FromStr ? Это может быть причиной вашей поломки.

@nvzqz это не так, хотя мне рекомендовали использовать TryFrom через Rust IRC, поскольку это более универсальное решение. TryFrom изначально работало отлично, пока несколько месяцев назад не произошло критическое изменение по ночам.

РЕДАКТИРОВАТЬ: Вы имеете в виду, что я должен переключиться на реализацию FromStr или что, если это _did_ impl FromStr , это может привести к поломке?

РЕДАКТИРОВАТЬ 2: Вот полный импл, довольно короткий и простой для любопытных: https://gist.github.com/kybishop/2fa9e9d32728167bed5b1bc0b9becd97

@kybishop есть ли конкретная причина, по которой вам нужна реализация для AsRef<str> , а не для &str ?

@sfackler У меня сложилось впечатление, что он позволяет конвертировать как &str , так и String , хотя я все еще немного новичок в Rust, поэтому, возможно, я неправильно понимаю, как именно AsRef<str> используется. Я попробую отключить &str и посмотреть, разрешает ли AsRef что-то, чего не позволяет &str .

@kybishop То же, что и https://github.com/rust-lang/rust/issues/33417#issuecomment -335815206, и, как говорится в сообщении об ошибке компилятора, это реальный конфликт с импл impl<T, U> std::convert::TryFrom<U> for T where T: std::convert::From<U> , который был добавлен в либкор.

Может быть тип T (возможно, в нижестоящем крейте), который реализует обаFrom<MyEnum> и AsRef<str>и имеет MyEnum: From<T> . В этом случае будут применяться оба импла, поэтому оба импла не могут существовать вместе.

@SimonSapin понял. Итак, какие есть варианты для людей, желающих конвертировать как с &str , так и с &String ? Просто нужно использовать TryFrom для обоих?

РЕДАКТИРОВАТЬ: Думаю, я могу просто съесть лишнюю работу и сделать колл .as_ref() на String s. Затем я могу просто иметь один TryFrom impl для str .

Да, два импла (возможно, один из которых основан на другом, как вы указали) должны работать, пока MyEnum не реализует From<&str> или From<String> .

@kybishop String реализует Deref<str> , поэтому вывод типа должен позволять &String преобразовываться в &str при передаче его в реализацию TryFrom . Вызов as_ref может потребоваться не всегда.

Если кто-то ищет быстрый пример этого: https://play.rust-lang.org/?gist=bfc3de0696cbee0ed9640a3f60b33f5b&version=nightly

Поскольку https://github.com/rust-lang/rust/pull/47630 вот-вот стабилизирует ! , есть ли желание PR заменить Infallible на ! здесь ?

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

type Infallible = !;

Просто присоединяюсь. Я согласен с @scottmcm по этому поводу.

Однако это добавляет дополнительные накладные расходы, поскольку эта функция ( TryInto / TryFrom ) теперь зависит от другой нестабильной функции — never_type .

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

Я хотел бы, чтобы ! стал достаточным словарным типом, чтобы Result<_, !> интуитивно читался как «безошибочный результат» или «результат, который (буквально) никогда не ошибается». Использование псевдонима (не говоря уже об отдельном типе!) кажется мне немного избыточным, и я вижу, что это вызывает, по крайней мере, у меня, мгновенную паузу при чтении подписи типа — «подождите, чем это отличалось от ! снова ?" ЮММВ, конечно.

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

Прошло два года с момента отправки RFC для этого API.

~ @briansmith : Идет пиар по стабилизации .~

РЕДАКТИРОВАТЬ : Или, может быть, я слишком устал...

Вы подключили PR стабилизации типа ! .

Поскольку PR стабилизации ! только что был объединен, я отправил PR для замены convert::Infallible на ! : #49038

https://github.com/rust-lang/rust/pull/49038 объединен. Я полагаю, что это был последний блокатор стабилизации. Пожалуйста, дайте мне знать, если я пропустил нерешенную проблему.

@rfcbot fcp слияние

rfcbot не отвечает, потому что другой FCP уже был выполнен ранее на https://github.com/rust-lang/rust/issues/33417#issuecomment -302817297.

Хм, есть несколько импликаций, которые следует пересмотреть. Теперь, когда у нас есть impl<T, U> TryFrom<U> for T where T: From<U> , оставшиеся импликации TryFrom с type Error = ! должны быть либо заменены имплами From , либо удалены, либо сделаны ошибочными ( изменение типа ошибки на некоторый необитаемый тип).

Те в этом случае, которые я могу найти, включают usize или isize . Я предполагаю, что соответствующие импликации From не существуют, потому что их ошибочность зависит от размера целевого указателя. Действительно, импликации TryFrom генерируются по-разному для разных целей: https://github.com/rust-lang/rust/blob/1.24.1/src/libcore/num/mod.rs#L3103 -L3179 ​​Это вероятно, представляет собой опасность переносимости.

генерируется по-разному для разных целей

Чтобы уточнить: разные тела методов для разных target_pointer_width в одном и том же impl — это хорошо (и, вероятно, необходимо), разные API (типы ошибок) — нет.

Стабилизационный PR: #49305. После некоторого обсуждения этот PR также удаляет некоторые импликации TryFrom , которые включают usize или isize , потому что мы не выбрали между двумя разными способами их реализации. (И мы, конечно, можем иметь только один.)

Специальная проблема отслеживания для них: https://github.com/rust-lang/rust/issues/49415 .

TryFrom отлично работал на rustc 1.27.0-nightly (ac3c2288f 2018-04-18) без ограничения функций, но сломался при компиляции с rustc 1.27.0-nightly (66363b288 2018-04-28) .

Были ли какие-либо регрессии в стабилизации этой функции за последние 10 дней?

@kjetilkjeka Недавно эта функция была нестабилизирована: #50121.

(Повторное открытие после отмены стабилизации)

@kjetilkjeka Стабилизация TryFrom была отменена в https://github.com/rust-lang/rust/pull/50121 вместе со стабилизацией типа ! из-за TryFrom<U, Error=!> for T where T: From<U> импл. Тип ! был нестабилизирован из-за https://github.com/rust-lang/rust/issues/49593.

Спасибо за объяснение. Означает ли это, что эта функция по существу заблокирована при некоторых изменениях приведения типов компилятора? Я не могу найти проблему, объясняющую, какие изменения требуются, известен ли масштаб изменений на данный момент?

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

https://github.com/rust-lang/rust/pull/49305#issuecomment-376293243 классифицирует различные возможные реализации трейтов.

@joshtriplett Насколько я понимаю, impl<T, U> TryFrom<T> for U where U: From<T> { type Err = !; } , в частности, не имеет обратной совместимости для добавления, если TryFrom уже был стабильным.

@SimonSapin
Почему это не обратно совместимо, чтобы добавить?

(И действительно ли нам нужна реализация одеяла?)

Почему это не обратно совместимо, чтобы добавить?

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

(И действительно ли нам нужна реализация одеяла?)

Я думаю, что общий имплемент действительно имеет смысл при написании универсального кода над TryFrom . Говоря обо всех типах, которые могут быть преобразованы в T , вы в большинстве случаев также захотите включить все типы, которые могут быть преобразованы в T наверняка. Я предполагаю, что альтернативой может быть требование, чтобы все также реализовывали TryFrom для всех типов, которые реализуют From , и ждали специализации, прежде чем делать общую реализацию. У вас по-прежнему будет проблема с тем, какие Err сделать Result общими. ! кажется хорошим способом как выразить, так и программно обеспечить безошибочность преобразования, и, надеюсь, стоит подождать.

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

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

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

Когда это стабилизируется: _пожалуйста_ добавьте TryFrom и TryInto в прелюдию.

@SergioBenitez Мы сделали это на Nightly некоторое время, и, к сожалению, это было серьезное изменение для ящиков, которые определяют свою собственную черту TryFrom или TryInto (для использования, пока std нестабильны), достаточно того, что мы вернули его.

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

Однако для TryFrom и TryInto решением было бы иметь другую прелюдию для издания 2018 года. Это неофициально обсуждалось раньше, но я не нашел его зарегистрированным, поэтому я открыл https://github.com/rust-lang/rust/issues/51418.

Общее решение аналогичной проблемы было реализовано для расширения
методы в https://github.com/rust-lang/rust/issues/48919 , в результате чего нестабильный
методы расширения выдают предупреждения, когда стабильный код сталкивается.

Кажется, что вы могли бы сделать что-то подобное с новыми терминами, добавленными в
прелюдия?

В четверг, 7 июня 2018 г., 11:47 Саймон Сапин, [email protected] написал:

@SergioBenitez https://github.com/SergioBenitez Мы сделали это на
Ночью какое-то время, и, к сожалению, это было переломным моментом для ящиков.
которые определяют свой собственный трейт TryFrom или TryInto (для использования в то время как стандартный
нестабильны), достаточно того, что мы вернули его.

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

Однако для TryFrom и TryInto решением было бы иметь другой
Прелюдия к выпуску 2018 года. Это обсуждалось ранее неофициально, но
Я не нашел его в файле, поэтому открыл #51418.
https://github.com/rust-lang/rust/issues/51418 .


Вы получаете это, потому что подписаны на эту тему.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/rust-lang/rust/issues/33417#issuecomment-395525170 ,
или заглушить тему
https://github.com/notifications/unsubscribe-auth/AAC2lNbHvgBjWBk48-1UO311-LuUY5lPks5t6XUvgaJpZM4IXpys
.

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

@SimonSapin , каков текущий план стабилизации TryFrom/TryInto? Я не смог найти ничего конкретного, кроме того, что он был возвращен 29 апреля.

@nayato Блокируется при стабилизации (опять же) типа never https://github.com/rust-lang/rust/issues/35121 , который сам заблокирован на https://github.com/rust-lang/rust/ выпуски/49593.

@SimonSapin Тип never используется только здесь . Можем ли мы стабилизировать TryFrom без этой реализации и приземлиться там, где никогда не стабилизируется? TryFrom — довольно важный интерфейс, даже если не считать эквивалентности с Try .

К сожалению нет. Было бы критическим изменением добавить этот общий импл после того, как трейт был стабилизирован, и у библиотек crates.io была возможность реализовать, например, TryFrom<Foo> for Bar , в то время как у них также есть From<Foo> for Bar , поскольку импликаторы перекрываются.

До сих пор было принято решение, что сделать TryFrom «совместимым» таким образом с From было достаточно важно, чтобы заблокировать стабилизацию.

Наверное, наивная и глупая мысль:

Что, если rustc тем временем получит постоянно включенный lint ошибок, который сработает для Error<_, !> , предотвратив любые импликации в пространстве пользователя и позволив позже добавить impl s ?

Или нестабильный impl<T: From<U>, U> TryFrom<U> for T с любым типом ошибки. Могут ли импли черт быть нестабильными?

@jethrogb Нет

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

Он заблокирован из-за этого импл:

impl<T, U> TryFrom<U> for T where T: From<U> {
    type Error = !;

    fn try_from(value: U) -> Result<Self, Self::Error> {
        Ok(T::from(value))
    }
}

@rust-lang/libs Что вы думаете о том, чтобы вернуться к enum Infallible {} вместо никогда, чтобы разблокировать?

Лично я не против иметь enum Infalliable {} , а затем изменить его на type Infalliable = ! .

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

Что мы помним, так это то, что мы можем сделать только одну такую ​​замену: если два или более стабильных стандартных библиотечных пустых перечисления (отдельных типа) позже станут одним и тем же типом, крейт, который содержит несколько impl для обоих, сломается. по мере того, как имплы становятся перекрывающимися (или идентичными). Например, impl From<std::convert::Invallible> for MyError и impl From<std::string::ParseError> for MyError .

Кстати, у нас есть std::string::ParseError , которое, насколько я могу судить, является единственным пустым перечислением в стандартной библиотеке 1.30.0. Итак , если мы можем быть уверены, что с этим планом нет других проблем, мы могли бы:

  • Переместить string::ParseError в convert::Infallible
  • Реэкспортируйте его в старое место с помощью pub use или pub type (это имеет значение?)
  • Используйте его в реализации одеяла TryFrom
  • Позже, в том же выпуске, что и при стабилизации типа never, замените string::ParseError и convert::Infallible на type _ = ! и используйте ! непосредственно там, где они использовались.
  • (Необязательно) Позже создайте предупреждения об устаревании для старых псевдонимов.

Только что и неохотно добавив трейт-заполнитель TryFrom в свой собственный ящик и, по общему признанию, не полностью понимая последствия RFC и усилий по стабилизации, я удивлен, что одеяло TryFrom за From с типом ошибки Infallible / ! , что держит это? Разве это не второстепенные цели после установления стабильных стандартных TryFrom и общих черт TryInto ? Я имею в виду, что даже без увеличения From до TryFrom (цель которого я не совсем понимаю), не будет ли меньше оттока, если каждый ящик, которому это нужно, не будет добавлять свои собственные TryFrom ?

Очевидная проблема, если TryFrom поставляется без общего внедрения для From , заключается в том, что ящики могут реализовывать TryFrom и From для одних и тех же типов (возможно, именно потому, что одеяла импл там нет), и они сломаются, когда одеяло импл будет добавлено в libstd.

Хотя, если подумать, может, со специализацией и не было бы кардинальных изменений?

Хм, пожалуйста, простите меня еще раз, если я упускаю очевидное . Это почти похоже на то, что эти 1,5-летние одиссеи RFC/отслеживания нуждаются в работающем разделе часто задаваемых вопросов, чтобы понять логическую прогрессию. Что, если в руководстве было просто ввести From только для безошибочных конверсий и TryFrom только для ошибочных конверсий? Разве это не дает аналогичный практический конечный результат, обеспечивая при этом меньший барьер для доставки в стандартной комплектации и корректировки ящиков?

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

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

Что необходимо для реализации плана, изложенного в https://github.com/rust-lang/rust/issues/33417#issuecomment -423073898? Что можно сделать, чтобы помочь?

Это немного неблагодарная задача, но было бы полезно, если бы кто-нибудь смог решить эту проблему с отслеживанием и https://github.com/rust-lang/rust/issues/35121 , чтобы проверить, есть ли проблема с тем планом, который мы мы обсуждали раньше и забыли с тех пор, в частности, может ли замена enum Infallible на type Infallible = !; после того, как перечисление было стабильным в предыдущем выпуске, быть критическим изменением.

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

@seanmonstar Нет, вы можете ссылаться на него, используя <u16 as TryFrom<u8>>::Error , и это считается стабильным именем. Свидетель:

// src/lib.rs
#![feature(staged_api)]
#![stable(since = "1.0.0", feature = "a")]

#[stable(since = "1.0.0", feature = "a")]
pub trait T1 {
    #[stable(since = "1.0.0", feature = "a")]
    type A;
}

#[unstable(issue = "12345", feature = "b")]
pub struct E;

#[stable(since = "1.0.0", feature = "a")]
impl T1 for u8 {
    type A = E;
}
// src/bin/b.rs
extern crate a;

trait T3 {}

impl T3 for <u8 as a::T1>::A {}
impl T3 for a::E {}

fn main() {}

Первый T3 impl не вызывает никаких ошибок. Только вторая реализация T3 вызывает ошибку E0658 «использование нестабильной функции библиотеки».

Это.... Вау, так и хочется, чтобы его укусили >_<

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

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

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

Например, с String::from_utf8 мы можем видеть, что тип ошибки содержит собственный ввод Vec<u8> , чтобы иметь возможность вернуть его :

// some invalid bytes, in a vector
let sparkle_heart = vec![0, 159, 146, 150];

match String::from_utf8(sparkle_heart) {
    Ok(string) => {
        // owned String binding in this scope
        let _: String = string;
    },
    Err(err) => {
        let vec: Vec<u8> = err.into_bytes(); // we got the owned vec back !
        assert_eq!(vec, vec![0, 159, 146, 150]);
    },
};

Итак, если бы мы получили реализацию String: TryFrom<Vec<u8>> , можно было бы ожидать, что <String as TryFrom<Vec<u8>>>::Error будет FromUtf8Error , верно?

Да, возврат входного значения в тип ошибки является допустимым вариантом. Взять ссылку с impl<'a> TryFrom<&'a Foo> for Bar — это другое.

Однако в этом конкретном примере я не уверен, что импл TryFrom подходит. UTF-8 — это только одна из возможных декодировок байтов в Unicode, и from_utf8 отражает это в своем названии.

Это немного неблагодарная задача, но было бы полезно, если бы кто-нибудь смог решить эту проблему с отслеживанием и # 35121, чтобы проверить, есть ли проблема с этим планом, который мы обсуждали раньше и с тех пор забыли, в частности, замена enum Infallible с type Infallible = !; после того, как перечисление было стабильным в предыдущем выпуске, может быть критическим изменением.

Никаких конкретных проблем с этим в этом выпуске или #35121 не было. Была одна проблема, связанная с тем, что ! может быть особенным в каком-то смысле, чем неограниченные типы. Но никаких проблем в PR, и из комментариев по обзору кода ясно, что перечисление стало стабильным (хотя этого никогда не происходило). Вот ссылки на то, что я нашел.

Оригинальная концепция
Одна абстрактная забота
Добро пожаловать от команды lib

С последующим:

44174 29 сент.: добавлен тип «Непогрешимый».

47630, 14 марта: стабилизируйте тип never !

49038 22 марта: тип Infallible преобразован в never !

49305, 27 марта: стабилизировано TryFrom

49518 30 марта: удалено из прелюдии

50121 21 апр: нестабилизированный

На заметку об общих последствиях, когда ! стабилизируется,

будет ли это работать?

impl<T, U> TryFrom<U> for T
where U: Into<T> {
    type Err = !;

    fn try_from(u: U) -> Result<Self, !> { Ok(u.into()) }
}

impl<T, U> TryInto<U> for T
where U: TryFrom<T> {
    type Err = U::Err;

    fn try_into(self) -> Result<U, !> { U::try_from(self) }
}

Таким образом, все безошибочные конверсии (с From и Into ) получают соответствующие импликации TryFrom и TryInto , где Err равно ! , и мы получаем импл TryInto за каждый импл TryFrom , подобно тому, как мы получаем импл Into за каждый импл From .

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

Затем мы можем проверить impl TryFrom<T, Err = !> или impl TryInto<T, Err = !> с подсказкой использовать impl From<T> или impl Into<T> .

Ах, не видел их (слишком много других импликаций загромождали документы). Можем ли мы изменить

impl<T, U> TryFrom<U> for T where    T: From<U>,

к

impl<T, U> TryFrom<U> for T where    U: Into<T>,

потому что это позволит строго увеличить количество импликаций и не будет наказывать людей, которые могут использовать только Into из соображений согласованности, таким образом они также получают автоматический импл для TryFrom и From impl будет применяться транзитивно из-за общего impl для Into .

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

Да, я хотел бы попробовать.

Есть ли случай, когда From<U> for T нельзя реализовать, но можно реализовать Into<T> for U и TryFrom<U> for T ?

Да, например

use other_crate::OtherType;

struct MyType;

// this impl will fail to compile
impl From<MyType> for OtherType {
    fn from(my_type: MyType) -> OtherType {
        // impl details that don't matter
    }
}

// this impl will not fail to compile
impl Into<OtherType> for MyType {
    fn into(self) -> OtherType {
        // impl details that don't matter
    }
}

Это связано с сиротскими правилами.
TryFrom и From одинаковы с точки зрения правил для сирот, и аналогично TryInto и Into одинаковы с точки зрения правил для сирот

@KrishnaSannasi ваш пример скомпилируется, просто посмотрите на этот пример игровой площадки

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

struct MyType<T>(T);

impl<T> From<MyType<T>> for (T,) {
    fn from(my_type: MyType<T>) -> Self {
        unimplemented!()
    }
}

impl<T> Into<(T,)> for MyType<T> {
    fn into(self) -> (T,) {
        unimplemented!()
    }
}

игровая площадка
Ах да, но как только вы добавляете общие параметры, он падает. Если вы попытаетесь скомпилировать каждый импл по отдельности, From не скомпилируется, а Into скомпилируется.


Еще одна причина, по которой я хочу это изменение, заключается в том, что в настоящее время, если вы пишете импл Into , вы не делаете и не можете автоматически получить импл TryInto . Я считаю это плохим дизайном, потому что он несовместим с From и TryFrom .

Я больше спрашиваю, есть ли когда-нибудь случай, когда вы хотели бы, чтобы TryFrom<U> for T автоматически производился от Into<T> for U , но где From<U> for T не может быть реализован. Мне это кажется бессмысленным.

Я, конечно, понимаю, что хочу использовать общую реализацию от Into до TryInto , но, к сожалению, система типов в настоящее время не допускает такую ​​общую реализацию, потому что она конфликтует с другими от From до TryFrom От From до Into общие импли (или, по крайней мере, я так понимаю).

Я совершенно уверен, что эти варианты использования были именно тем, что должен был исправить связанный RFC @kjetilkjeka . После того, как это реализовано, никогда не должно быть случая, когда вы можете реализовать Into , но не From .

@scottjmaddox Я не обязательно хочу, чтобы Into подразумевал TryFrom настолько, насколько я хочу, чтобы Into подразумевал TryInto . Также From и Into семантически эквивалентны, только потому, что мы не можем выразить это в нашей системе признаков, не означает, что

TryFrom<U> for T будет автоматически получен из Into<T> for U , но where From<U> for T не может быть реализован.

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

@clarcharr Даже если это так, по-прежнему невозможно, чтобы и Into подразумевали TryInto , и TryFrom подразумевали TryInto напрямую, как два одеяла impls будет конфликтовать. Я хотел бы, чтобы Into подразумевало TryInto , а TryFrom подразумевало TryInto . Способ, который я предлагаю, сделает это, хотя и косвенно.


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


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

Просто для ясности, я хочу, чтобы эти четыре импликации

  • From подразумевает Into (для стабильности)
  • TryFrom подразумевает TryInto или TryInto подразумевает TryFrom (поскольку они семантически эквивалентны)
  • From подразумевает TryFrom
  • Into подразумевает TryInto

Меня не волнует, если они сделаны прямо или косвенно.

Или мы могли бы сделать TryInto псевдонимом:

trait TryInto<T> = where T: TryFrom<Self>;

Я понятия не имею, сработает ли это на самом деле, но это кажется достаточно простым.

Где будет определен метод into ?

@clarcharr Как сказал @SimonSapin , где будет определен метод into. У нас нет псевдонимов черт. Это должен быть другой RFC. И нам нужно было бы заблокировать это на этом RFC, что нежелательно.

@KrishnaSannasi В настоящее время невозможно иметь все четыре из From => Into , From => TryFrom , TryFrom => TryInto и Into => TryInto , потому что все, что реализует From , будет есть два конкурирующих внедрения для TryInto (один из From => Into => TryInto , а другой из From => TryFrom => TryInto ).

Поскольку From => Into и TryFrom => TryInto являются критическими, Into => TryInto приходится жертвовать. Или, по крайней мере, это мое понимание.

Да, я не думал, что псевдоним TryInto правильно, так что просто игнорируйте меня ><

Я согласен с тем, что говорит @scottjmaddox .

@scottjmaddox

Чтобы устранить возможное недоразумение, я удаляю $ From auto impls TryFrom и заменяю его Into auto impls TryFrom .

В настоящее время невозможно иметь все четыре из From => Into, From => TryFrom, TryFrom => TryInto и Into => TryInto.

Это просто неверно, просто посмотрите на предложенную реализацию.

Вот этот:
From -> Into -> TryFrom -> TryInto

From автоматическая реализация Into
Into автоматическая реализация TryFrom
TryFrom автоматическая реализация TryInto

Кроме того, из-за транзитивных автоматических импликаций мы получаем
From подразумевает TryFrom (поскольку From автоматически подразумевает Into и Into автоматически подразумевает TryFrom )
Into подразумевает TryInto (потому что Into автоматически подразумевает TryFrom и TryFrom автоматически подразумевает TryInto )
каждый с 1 уровнем косвенности (я думаю, это нормально)

Так что все мои условия соблюдены.
Мы могли бы иметь все

Внедрение | уровни косвенности
-----------------------|------------------
From подразумевает Into | Нет косвенности
TryFrom подразумевает TryInto | Нет косвенности
From подразумевает TryFrom | 1 уровень косвенности
Into подразумевает TryInto | 1 уровень косвенности


From -> Into -> TryInto -> TryFrom
Также будет работать, но я хотел бы сохранить согласованность, а исходная версия (см. выше) более последовательна, чем эта версия.


Примечание по терминологии: (когда я их использую)

-> означает автоматическую реализацию
auto impl ссылается на фактически введенный импл.
подразумевает означает, что если у меня есть этот импл, я также получу этот импл


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

запрос на вытягивание

@KrishnaSannasi Аааа, теперь я понимаю твою точку зрения. Да, это имеет смысл, и теперь я вижу, как предложенное вами изменение решает проблему и обеспечивает желаемое поведение. Спасибо за подробное объяснение!

Редактировать : ладно, подождите, хотя... Я до сих пор не понимаю, почему нынешних реализаций одеяла недостаточно. Предположительно, есть случай, когда вы сможете реализовать Into , но не From ? И все же возможно автоматическое получение TryFrom ?

Редактировать 2 : Хорошо, я только что вернулся и прочитал ваше объяснение о том, как правило сиротства может предотвратить реализацию From . Все это имеет смысл сейчас. Я полностью поддерживаю это изменение. До сих пор немного расстраивает то, что правило сиротства имеет так много непредвиденных последствий, но мы не собираемся исправлять это здесь, поэтому имеет смысл сделать все возможное.

Захочет ли кто-нибудь из @rust-lang/libs запустить FCP на этом теперь, когда https://github.com/rust-lang/rust/issues/49593 исправлено, а never_type является кандидатом на стабилизацию снова?

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

После того, как стабилизационный PR для типа never получен, мы можем создать (новый) стабилизационный PR для TryFrom / TryInto и использовать там rfcbot, чтобы убедиться, что члены команды его видят.

Можно ли изменить существующие пустые типы перечислений, такие как FromStringError , на псевдонимы для ! наряду со стабилизацией?

@clarcharr Да. Из-за когерентности реализации трейтов мы можем сделать это только для одного типа. К счастью, у нас есть только std::string::ParseError . (И именно поэтому мы использовали его вместо добавления нового типа для impl FromString for PathBuf в https://github.com/rust-lang/rust/pull/55148.)

Однако это не связано с TryFrom / TryInto . Это тема для https://github.com/rust-lang/rust/issues/49691 / https://github.com/rust-lang/rust/issues/57012 , так как это должно произойти в том же цикле выпуска. как стабилизация ! .

@SimonSapin Можно ли объединить мой запрос на извлечение (# 56796) до того, как это стабилизируется?

Конечно, я добавил это в описание проблемы, чтобы мы не потеряли след.

Спасибо!

Можно ли это интегрировать в стабильную версию? Это зависимость от argdata для CloudABI.

@mcandre Да. В настоящее время он находится на странице https://github.com/rust-lang/rust/issues/57012 , которая недавно была разблокирована. Я надеюсь, что TryFrom появится в Rust 1.33 или 1.34.

Мне надоело этого ждать и у меня появилось свободное время (наконец-то). Если есть какой-либо код или документация, которые я мог бы сделать, чтобы продвинуть это вперед, я добровольно помогу.

56796 было объединено. Так что мы можем это проверить.

@icefoxen Я думаю, что сейчас лучшее, что вы можете сделать, это предоставить отзывы (и изменения) о документах этих черт. Прямо сейчас я нахожу их немного недостаточными по сравнению с чертами From и Into .

Работа над документацией. Небольшое препятствие: я хочу assert_eq!(some_value, std::num::TryFromIntError(())); . Однако, поскольку TryFromIntError не имеет конструктора и общедоступных полей, я не могу создать его экземпляр. Любые советы о том, как действовать? Пока я просто делаю assert!(some_value_result.is_err()); .

Извините, если это не то место, но похоже, что в документах для TryFromIntError есть ошибка - в нем указан impl Debug for TryFromIntError , хотя на самом деле для него нет кода. В настоящее время компилятор генерирует ошибку при попытке развернуть результат использования TryFrom.

Есть ли в этом смысл fmt::Debug ?

@marco9999

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct TryFromIntError(());

У него есть атрибут #[derive(Debug)] .

Хм, да есть... что-то не работает должным образом?

error[E0599]: no method named `unwrap` found for type `std::result::Result<usize, <T as std::convert::TryInto<usize>>::Error>` in the current scope
  --> src\types\b8_memory_mapper.rs:67:51
   |
67 |         let address: usize = T::try_into(address).unwrap();
   |                                                   ^^^^^^
   |
   = note: the method `unwrap` exists but the following trait bounds were not satisfied:
           `<T as std::convert::TryInto<usize>>::Error : std::fmt::Debug`

@ marco9999 marco9999 Возможно, вам не хватает общего ограничения. TryFromIntError используется только некоторыми типами, но ваш T может быть любым:

fn foo<T: TryInto<u8>>(x: T) -> u8
where
    <T as TryInto<u8>>::Error: Debug,
{
    x.try_into().unwrap()
}

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

Я хочу assert_eq!(some_value, std::num::TryFromIntError(()));

@icefoxen Нет никакой полезной ценности, связанной с TryFromIntError , поэтому такое утверждение не имеет большой ценности. Если у вас есть Result<_, TryFromIntError> , а это Err , другого значения быть не может.

assert!(some_value_result.is_err());

Мне это кажется разумным.

Перекрестные ссылки: https://github.com/rust-lang/rust/pull/58302

Спасибо @glaebhoerl.

В связи с исправлением ошибки блокировки (https://github.com/rust-lang/rust/issues/49593) я надеялся, что тип never можно будет стабилизировать в ближайшее время® https://github.com/rust-lang/ rust/issues/57012 и разблокируйте его. Однако возникла новая проблема (https://github.com/rust-lang/rust/issues/57012#issuecomment-460740678), и у нас также нет единого мнения по другой (https://github.com). /rust-lang/rust/issues/57012#issuecomment-449098855).

Итак, на собрании libs на прошлой неделе я снова поднял идею, я полагаю, впервые предложенную @scottmcm в https://github.com/rust-lang/rust/issues/33417#issuecomment -299124605, для стабилизации TryFrom и TryInto без типа never , а вместо этого имеют пустое перечисление, которое позже можно сделать псевдонимом для ! .

В прошлый раз, когда мы это обсуждали (https://github.com/rust-lang/rust/issues/33417#issuecomment-423069246), мы не могли вспомнить, почему не сделали этого в прошлый раз.

На прошлой неделе @dtolnay напомнил нам о проблеме: до того, как ! станет полным типом, его уже можно использовать вместо возвращаемого типа функции, чтобы указать, что она никогда не возвращается. Сюда входят типы указателей на функции. Итак, если предположить, что https://github.com/rust-lang/rust/pull/58302 попадает в этот цикл, такой код будет действителен в Rust 1.34.0:

use std::convert::Infallible;
trait MyTrait {}
impl MyTrait for fn() -> ! {}
impl MyTrait for fn() -> Infallible {}

Поскольку fn() -> ! и fn() -> Infallible — это два разных типа (указателей), два импликатора не перекрываются. Но если мы заменим пустое перечисление псевдонимом типа pub type Infallible = !; (когда ! станет полным типом), то два импла начнут перекрываться, и код, подобный приведенному выше, сломается.

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

  • Нужно приложить все усилия, чтобы создать код, который нарушен этим изменением, поэтому на практике это вряд ли произойдет.
  • Мы будем использовать Кратер, чтобы получить дополнительный сигнал, когда придет время.
  • Если мы придем к выводу, что поломка достаточно значительна, наличие и типа never, и пустого перечисления с одной и той же ролью — это несоответствие, с которым мы можем смириться.

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

58015 должен быть готов к рассмотрению/объединению сейчас.

@kennytm Разве нельзя уже ссылаться на ! в стабильной версии? Например, рассмотрим следующее:

trait MyTrait {
    type Output;
}

impl<T> MyTrait for fn() -> T {
    type Output = T;
}

type Void = <fn() -> ! as MyTrait>::Output;

После этого Void ссылается на тип ! .

Это похоже на ошибку, а значит, на нее не распространяются гарантии стабильности. Использование типа never ( ! ) в качестве типа в любом качестве по-прежнему нестабильно, по крайней мере, до слияния #57012.

Как я могу помочь с документацией? :-)

О, я думал, что https://github.com/rust-lang/rust/pull/58015 приземлился, но его еще нет… Давайте обсудим это там.

Может ли трейт TryFrom иметь метод для проверки возможности преобразования аргумента без его использования?

fn check(value: &T) -> bool

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

К сожалению, это должно было быть закрыто https://github.com/rust-lang/rust/pull/58302. Закрытие сейчас.


@ o01eg Типичный способ преобразования без потребления - это реализовать, например, TryFrom<&'_ Foo> вместо TryFrom<Foo> .

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

Нет, мы закрываем проблемы с отслеживанием, когда PR, стабилизирующий функцию, попадает на мастер.

Нет, обычно мы закрываем проблему с отслеживанием, когда стабилизация или удаление попадает в ветку master . После этого отслеживать уже нечего. (Если только не появится недавно зарегистрированная ошибка, но мы обработаем ее отдельно.)

Проблемы с отслеживанием закрываются PR, который их стабилизирует. В зависимости от цикла выпуска до выпуска стабильной версии может пройти до 12 недель.

Понятно. Спасибо за разъяснения, всем! :)

@gregdegruy обновите вашу версию Rust до 1.34 или выше.

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