Pull to refresh

Comments 35

На самом деле здесь лучше использовать метод to_owned(), поскольку метод to_string() для размещения строки в памяти использует довольно тяжёлую библиотеку форматирования текста, а to_owned() просто копирует строковый срез &str напрямую в новый объект String — прим. перев.
То чувство, когда переводчик разбирается в предмете лучше автора.

Зачем вообще в расте разделение на String и &str? Почему не используется один тип, как в остальных языках?
Потому, что, String владеет буфером, а значит &String указывает на обьект владеющий буфером. Но статическим буфером (строковым литералом) никто владеть не может, так что для него придумали тип str, который сам по себе непонятно что собой представляет, но вот &str — это указатель на строковый литерал, что уже полезно.
Его можно было бы запихнуть и в &String, но тогда нужно делать каждый раз аллокацию/деаллокацию. Итого, N аллокаций по одной на каждый вызов кода со строковым литералом против нуля с &str. Профит!

Насчет лучше разбирается: я думаю, это культурное различие и автор хотел сделать код и обьяснение понятнее новичкам.
Может, через какое-то время эта оптимизация все-таки станет менее актуальной — https://github.com/rust-lang/rust/pull/30652
Возможно, когда-нибудь. Но пока так.
UFO just landed and posted this here
Сейчас метод to_string() определёт в трейте ToString, у которого есть blanket-реализация для всех Display-типов:
impl<T: Display> ToString for T {
    fn to_string(&self) -> String {
        format!("{}", self)
    }
}


Соответственно, для String/&str также используется format!(), что неэффективно.

Как только специализация будет доступна, можно будет переопределить ToString для str через String::from_str(). Без специализации сделать такую реализацию нельзя, потому что она будет конфликтовать с blanket-реализацией для T: Display.
Вообще говоря, вы не правы. Rust многие (в том числе я), видят правопреемника C++. Так вот, в C++ есть некоторый аналог среза, когда в функцию надо передать часть строки без копирования. Правда делается это, обычно, двумя параметрами — либо указателем на начало строки и длиной строки, либо двумя итераторами. Первый способ считается более «сишным» и несколько менее красивым. В Rust просто создали отдельную сущность для этого, называемую срезом, у которой внутри всё те же два параметра. Плюс, всё это отлично легло на концепцию владения (которая и так давно есть в языках типа С и С++, просто не поддерживается на уровне языка).
а почему в new нельзя просто принимать Into без генерик-параметра?
простите, парсер съел. я имел ввиду:
а почему в new нельзя просто принимать тип без генерик-параметра? аля
fn new(name: Into<String>)
Да, это типаж. Т.е. типаж нельзя указать в качестве типа параметра функции?
У переменной должен быть тип. Into<String\> — это не тип, это трейт. Следственно, запись
name: Into<String>
не имеет смысла.
Т.е. у функции всегда точный тип параметра, только в случае с дженериком, для каждого использования компилятором будет создана своя функция с конкретным типом?
Да, это называется мономорфизация. Есть и вариант с динамическим полиморфизмом, про него ниже написали.
Для полноты картины, некоторые типажи можно использовать в качестве типов (так называемые «типажи-объекты», а типажи, которые можно использовать как объекты называются «объектно безопасными» (object safe)), но только по указателям (вроде &Display), т.к. их размер не известен во время компиляции. Но типаж Into<T> к таким не относится, т.к. принимает тип-параметр.
В этом случае типаж — это тоже не тип, а для каждого object safe-типажа неявно создается тип &TraitName, для которого авоматически создается реализация трейта TraitName, и этот тип — что-то вроде класса в ООП-языках, в обьектах этого типа будет таблица виртуальных функций, вместо мономорфизации.
«Указатель на трейт» — тоже бессмысленная фраза, так как трейт не является типом.
Спасибо, это сильно улучшило моё понимание трейтов!
Получается этакий алгебраический тип объединяющий все реализации типажа?
Типа того, да! Как бы енум, но без паттернматчинга.
Но только логически. Фактически, я думаю, это указатель на кучу, под которым vptr и байты исходной структуры. Насколько я понимаю, обычно енумы делают через tagged union.
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
А так же Arc<TraitName>, и вообще любой тип, реализующий Deref<Target=TraitName>.
Не любой. Насколько я помню, значение дженерикового типа должно быть последним элементом в структуре, чтобы быть unsized.
Здесь это не важно абсолютно, Deref<Target=T> преобразует заданный объект в &T, его метод `deref(&self) -> &Self::Target` принимает self по ссылке, поэтому unsized или sized тут Self и Self::Target не играет никакой роли.
Вот пользовательский тип может быть unsized только если unsized-поле единственное unsized поле и последнее в списке полей. Но только к трейту Deref и deref coercion это не имеет никакого отношения.
Спасибо, это многое объясняет.
UFO just landed and posted this here
Into::into() принимает self по значению, такие трейты не могут быть object-safe, следовательно, из них нельзя сделать трейт-объект.
Всё так, но не так.

1) Метод Into::into принимает self по значению, это верно. По значению можно передавать только Sized типы, потому что для передачи по значению размер типа должен быть известен при компиляции. Так что stepik777 тут не прав, это не искусственное ограничение, для него есть объективная причина.
2) Googolplex не совсем прав по поводу того, что типажи, у которых есть методы, принимающие self по значению — не object-safe. Если на сам типаж нет ограничения `trait TraitName: Sized`, то он может оказаться object-safe даже с методами, которые принимают self по значению, просто компилятор потребует, чтобы такие методы содержали в сигнатуре ограничение where Self: Sized. См. например типаж Iterator, который object-safe, но у него есть методы, принимающие self по значению doc.rust-lang.org/src/core/iter.rs.html#376.
3) Я сам ошибся, когда говорил, что генерик параметр в трейте Into мешает ему быть object-safe, не мешает. Просто компилятор попросит конкретизировать тип при приведении к трейт-объекту, чтобы T в Into был конкретным типом. То есть нету типаж-объекта &Into<T> где T какой-то параметр, но может быть типаж-объект &Into<u32>.

По итогу stepik777 по большому счёту прав, что Into<T> не может быть типаж-объектом только из-за ограничения на Sized для всего типажа, если бы только ограничения на весь типаж не было, а только метод into имел сигнатуру fn into(self) -> T where Self: Sized, то сделать такой типаж-объект, конкретизировав параметр T, было бы можно. НО это не имело бы никакого смысла, так как вызвать на таком типаж-объекте единственный метод into было бы невозможно в принципе, так как он принимает self по значению, так что использование такого типаж-объекта было бы абсолютно бессмысленно.
Вообще, дженерики в виде трейтов без типов высшего порядка — отстой.
Например, нельзя для всех коллекций обьектов типа T where T: Display реализовать Display:
impl<F, T> Display for F<T> where F: Collection<T>, T: Display {...} // oops, compile error


Пока их нет, не могу получить наслаждения от раста, если кодить что-то обобщенное.
Пока кодишь без дженериков — все круто, как только хочется чуть серьезнее, чем очень простые случаи с ними — все, грусть навевает.
Аналогично, тоже их очень жду. Пока просто обхожусь тем, что есть (ассоциированные типы).
Проблема еще в том, что если их сделают, придется здорово перелопатить стандартную либу, если захочется красиво и идиоматично.
А они ее уже преждевременно стабилизировали напрочь.
Сложная ситуация.
UFO just landed and posted this here
Да, конечно. Но тут не хотелось бы заострять внимание на deref coercion, так как речь не про неё, а про повторяющиеся преобразования.
Тут специально делается акцент на происходящих переходах значение-ссылка-значение, поэтому выбран самый явный метод.
Sign up to leave a comment.

Articles