LINUX.ORG.RU

Зачем в rust нужно keyword «impl» в параметрах и в возвращаемом значении функций?

 ,


0

7

Ау, растаманы! Почему нельзя вместо

fn f(x: &impl Trait1) -> impl Trait2 

писать

fn f(x: &Trait1) -> Trait2 

? Trait – не first class тип? Это бред, но даже если так: и что? Или может возможен конфликт одинаковых имён структуры и trait? Короче, смахивает на синтаксический оверхед.

UPD. Ну и вдогонку, чтобы дважды не вставать: «we can’t implement external traits on external types» – это не просто дичь, а просто дичь. Ну и аргументация трындец: «Without the rule, two crates could implement the same trait for the same type, and Rust wouldn’t know which implementation to use.». Вот когда конфликт возникнет, тогда и ругались бы на него.

UPD2. fn vs fun – зачем отдельный keyword для лямбд?

★★★★★

Последнее исправление: dimgel (всего исправлений: 3)
Ответ на: комментарий от meliafaro

https://doc.rust-lang.org/book/ch10-02-traits.html

Правильно ли я понимаю, что растаманы не знают раста? Или ты не растаман, и просто так ответил?

dimgel ★★★★★
() автор топика
Последнее исправление: dimgel (всего исправлений: 1)

Trait – не first class тип?

Trait – это не тип. Если бы Rust умел нормально Existential Types (dyn Trait не очень считается, сорри), то твой вариант бы работал. А так, упс.

Можешь вот тут почитать: https://varkor.github.io/blog/2018/07/03/existential-types-in-rust.html. Это не совсем про твой вопрос, но близко.

hateyoufeel ★★★★★
()
Последнее исправление: hateyoufeel (всего исправлений: 1)
Ответ на: комментарий от hateyoufeel

Trait – это не тип.

Дожили. Это уже даже не умножение сущностей без необходимости, это какое-то новое слово в CS.

Можешь вот тут почитать:

Я сначала книжку дочитаю. А потом забью. Т.к. уже понятно, что как замена плюсов не годится: тут своих тараканов хватает. Причём если плюсовые тараканы – чисто из-за легаси, то здесь – горе от ума.

dimgel ★★★★★
() автор топика
Ответ на: комментарий от meliafaro

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

dimgel ★★★★★
() автор топика
Ответ на: комментарий от dimgel

Дожили. Это уже даже не умножение сущностей без необходимости, это какое-то новое слово в CS.

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

Я сначала книжку дочитаю. А потом забью.

Ну и хер с тобой тогда, чо ¯\_(ツ)_/¯

hateyoufeel ★★★★★
()
Ответ на: комментарий от hateyoufeel

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

Т.е. по-твоему «не тип» и «не конкретный тип» – это одно и то же? И своё вольное обращение с терминологией ты теперь оправдываешь намёками на мою глупость? Т.е. переходом на личности. Боюсь, глупец из нас двоих тут ты.

UPD. Ну а различный синтаксис для параметров абстрактных и конкретных типов – причём в условиях, когда ничто не мешало сделать синаксис одинаковым (обратное мне не объяснили) – по-прежнему идиотизм.

dimgel ★★★★★
() автор топика
Последнее исправление: dimgel (всего исправлений: 1)
Ответ на: комментарий от dimgel

И своё вольное обращение с терминологией ты теперь оправдываешь намёками на мою глупость? Т.е. переходом на личности. Боюсь, глупец из нас двоих тут ты.

Ты прямо как моя бывшая: сама придумала, сама обиделась! xD

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

hateyoufeel ★★★★★
()
Последнее исправление: hateyoufeel (всего исправлений: 2)
Ответ на: комментарий от dimgel

dimgel ★★★ (28.05.21 01:52:19) истеричка

Лол, ну каждый раз же)

meliafaro ★★★★★
()
Ответ на: комментарий от hateyoufeel

Ты прямо как моя бывшая: сама придумала, сама обиделась! xD

И опять переход на личности.

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

Разрывает у меня из-за другого. А вот из таких невинных особенностей и состоит красота (или отсутствие оной) языка.

Дальше будет только хуже. Так что просто забей, пока не поздно.

Без сопливых разберусь.

Не для тебя его делали.

Спасибо, кэп.

dimgel ★★★★★
() автор топика
Ответ на: комментарий от dimgel

И опять переход на личности.

Блин! Да ты реально как моя бывшая! Тебя случайно не Катей зовут? 0_o

А вот из таких невинных особенностей и состоит красота (или отсутствие оной) языка.

Ещё немного почитаешь книжку про Rust и поймёшь, зачем там слово impl и чем поведение трейта в данном случае отличается от поведения конкретного типа. Давай! Ты сможешь! По ссылке, которую я привёл выше, об этом, кстати, тоже написано.

hateyoufeel ★★★★★
()
Последнее исправление: hateyoufeel (всего исправлений: 1)
Ответ на: комментарий от dimgel

Заигнорил обоих чуваков в треде, которые пытались тебе что-то объяснить. Ну точно как моя бывшая! Прямо один в один!

@meliafaro, дай пять! Мы победили ТСа! xD

hateyoufeel ★★★★★
()

В С++, кстати, точно также.

Есть функции с конкретными типами, а есть семейства функций с ограничениями на тип.

Только в С++ решили переиспользовать auto вместо нового слова, но смысл не поменялся…

https://gcc.godbolt.org/z/dEafE3fvK

Так что претензия не понятна.

fsb4000 ★★★★★
()
Ответ на: комментарий от fsb4000

Ну auto это всё-таки про вывод типов. Твой пример более подходит под «trait bound syntax» в расте: «fn f<T: Trait>(x: &T) { x }». (UPD: А может и не более. Чёт я туплю. Зачем два синтаксиса, кстати, – отдельный вопрос: так себе синтаксический сахар.)

А претензия в том, что слово «impl» в примере в заглавном посте тупо лишнее – компилятор что, не способен разобраться, что Trait1 и Trait2 – идентификаторы trait-ов?

Как и fun vs fn: если нет идентификатора, а за кейвордом сразу следует открывающая скобка (список параметров), значит анонимная функция, и городить отдельный кейворд не было никакого смысла.

dimgel ★★★★★
() автор топика
Последнее исправление: dimgel (всего исправлений: 1)
Ответ на: комментарий от dimgel

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

anonymous
()

Трейты это не типы, это - классы типов. fn foo(_: Foo1) -> Foo2 и fn bar(_: impl Bar1) -> impl Bar2 пишутся по разному, потому что означают совершенно разные вещи.

Первое - это просто функция Foo1 -> Foo2, второе - дженерик функция, принимающая значение любого типа реализующего трейт Bar1 и возвращающая экзистенциальный тип, реализующий трейт Bar2.

То есть вторая функция это на самом деле fn bar<T: Bar1>(_: T) -> impl Bar2

Лучше сразу видеть по сигнатуре с чем имеем дело, чем вспоминать что такое Foo1 - тип или трейт.

Ну и ещё есть dyn Trait - трейт-объект, используемый для динамической диспетчеризации. Раньше было позволено dyn не писать и fn f(x: &Foo) означало fn f(x: &dyn Foo) (а, не &impl Foo), если Foo - это трейт.

Насчёт правил когерентности, запрещающих писать реализации чужих трейтов для чужих типов - это долгий разговор. Можно кое-что почитать тут https://github.com/Ixrec/rust-orphan-rules

Вкратце: предпочли меньше поломок зависимостей при обновлении библиотек.

Ключевого слова fun в расте нет.

red75prim ★★★
()
Последнее исправление: red75prim (всего исправлений: 1)
Ответ на: комментарий от anonymous

Возвращать или передавать по значению trait-object нельзя.

Это понятно и логично: размер конкретного типа неизвестен (чай не жава, где всё в куче).

Потому что Trait - это trait-object, так же как и dyn Trait (второе это новый синтаксис как раз для того чтобы не было неоднозначности).

Мои претензии – чисто к синтаксису. Грубо говоря – к парсеру, строящему AST из сорцов. Пока что по-прежнему не вижу, каким боком тут отличия конкретных и абстрактных типов.

Ладно, дочитаю книгу до dyn trait-ов, авось там вылезет какая-нибудь новая синтаксическая закорючка, из-за которой без кейворда impl в параметрах функции, компилятор сам не в состоянии по идентификатору разобраться. (Ты кстати второй после заигноренного, кто советует дотуда точитать. Небось разлогинился и из-под анонима пишешь? Надо было анонимные каменты-то запретить. :) )

dimgel ★★★★★
() автор топика

Трейты - это интерфейсы, они не могут быть инстанцированы, только реализованы каким-либо объектом.

anonymous
()
Ответ на: комментарий от red75prim

Лучше сразу видеть по сигнатуре с чем имеем дело, чем вспоминать что такое Foo1 - тип или трейт.

А какая разница? Впрочем, в свете невозможности возврата trait по значению, на практике возможно разница и есть. (Подозреваю, что dyn – как раз про это.) Ладно, как говорил тренер, когда его толпой валили – забодали. :)

Насчёт правил когерентности, запрещающих писать реализации чужих трейтов для чужих типов - это долгий разговор. Можно кое-что почитать тут https://github.com/Ixrec/rust-orphan-rules

Сенькс, это гляну.

dimgel ★★★★★
() автор топика
Ответ на: комментарий от anonymous

Трейты - это интерфейсы, они не могут быть инстанцированы, только реализованы каким-либо объектом.

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

dimgel ★★★★★
() автор топика
Ответ на: комментарий от dimgel

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

Haskell и прочее семейство ML

hateyoufeel ★★★★★
()
Последнее исправление: hateyoufeel (всего исправлений: 1)
Ответ на: комментарий от hateyoufeel

Лол, проиграл со скоротечности беседы) Ждём следующих раскулаченных

meliafaro ★★★★★
()
Ответ на: комментарий от dimgel

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

anonymous
()
Ответ на: комментарий от anonymous

Был синтаксис Trait, он означал trait-объект с динамической диспетчеризацией, если Trait сделать синтаксисом для экзистенциальных типов, то поменялось бы поведение существующих конструкций, даже если они теперь deprecated.

Ага! Т.е. уже и тут легаси… :) Ну вот наконец-то - спасибо за разъяснение!

Ну и impl Trait более однозначный синтаксис, сразу понятно что имеется в виду конкретный тип, а не то, что сейчас пишется как dyn Trait.

Само по себе это плохой аргумент. Параметр (и переменная вообще) – это имя + тип. Уточнять что именно за тип – это избыточная многословность, всё равно что на плюсах писать: f(x: enum T), f(x: class T) и т.д. Кому надо – тот в имя параметра нужную информацию закодирует. Как в WinAPI принято: lpcszName. %)

dimgel ★★★★★
() автор топика
Ответ на: комментарий от dimgel

Разница между трейтом и типом, в отличие от с++, принципиальная, и она всегда подчёркивается, в частности, на уровне синтаксиса.

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

anonymous
()
Ответ на: комментарий от anonymous

Угу, уже догадался. Ладно, читаю дальше, там авось утро вечера мудренее будет.

dimgel ★★★★★
() автор топика
Ответ на: комментарий от dimgel

Это не избыточная многословность. Если я напишу fn ambiguous(arg: &Trait1) -> Box<Trait2> - что я тут имел в виду?

anonymous
()
Ответ на: комментарий от anonymous

Это не избыточная многословность. Если я напишу fn ambiguous(arg: &Trait1) -> Box - что я тут имел в виду?

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

А про возвращаемое значение я хз, до Box ещё не дочитал. Подозреваю, это умный указатель на кучу? Дык при наличии нормального полиморфизма и тут должно быть всё ок: удаление будет с кучи же.

dimgel ★★★★★
() автор топика
Ответ на: комментарий от dimgel

Разница принципиальная, потому что impl Trait сгенерирует отдельную функцию для каждого конкретного типа на месте аргумента и возвращаемый тип тоже будет конкретный и известный на этапе компиляции. Никакой дополнительной цены за динамическую диспетчеризацию гарантированно не будет. В случае с dyn Trait функция будет одна вне зависимости от того сколько разных типов будешь передавать в функцию и возвращаемый тип может быть любым, что реализует Trait, но ты будешь платить за это тем, что возвращаемый объект должен быть динамически выделен на куче и динамической диспетчеризацией (которая совсем не бесплатна).

anonymous
()
Ответ на: комментарий от anonymous

И да, если уж плюсы в пример привел - там вообще аналога impl Trait не было раньше, может сейчас и есть если концепты уже ввели, давно не смотрел что там происходит. Самое близкое - что-то вроде auto function(...) -> decltype(...) что по синтаксису и читаемости проигрывает вообще всему.

anonymous
()
Ответ на: комментарий от anonymous

Про кодогенерацию – понятно. Если передавать по значению, то специализации неизбежны, тут без вариантов. Хотя в твоём примере оно наверное не нужно (где параметр по ссылке, а результат вроде бы на куче).

Я тут попробовал и удивился: оказывается, gcc не ругается когда я в плюсах возвращаю по значению подкласс из функции, тип возврата которой – суперкласс: https://gcc.godbolt.org/z/nrenozYE3 Была уверенность, что уж такую-то дичь, когда sizeof(B) > sizeof(A), должен ловить.

// Это я пытался прикинуть, настолько ли нужно явно указывать: не может ли компилятор автоматом из возврата по значению вывести impl, и т.п.

dimgel ★★★★★
() автор топика
Последнее исправление: dimgel (всего исправлений: 2)
Ответ на: комментарий от dimgel

Потому что sizeof(B) > sizeof(A). При возврате значение обрубается.

Нет, конечно же. Там всё норм. (ну всмысле деструктор не виртуальный и т.д., но с возвратом там всё норм)

При возврате вызывается copy constructor.

сравни:

https://gcc.godbolt.org/z/1TsWGxbxM

и

https://gcc.godbolt.org/z/PqqE6K47q

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

fsb4000 ★★★★★
()
Последнее исправление: fsb4000 (всего исправлений: 2)
Ответ на: комментарий от fsb4000

Вот дьявол, об этом я и не подумал… С другой стороны, к пониманию вреда (UPD: неявных) narrowing conversions для примитивов комитет уже пришёл (хотя от «y{x}» место «y=x» у меня в глазах рябит и скулы сводит – я в своём примере даже «B()» написал вместо «B{}»), так что почему бы и здесь не выкинуть warning при отсутствии явного приведения типов?

dimgel ★★★★★
() автор топика
Последнее исправление: dimgel (всего исправлений: 1)
Ответ на: комментарий от dimgel

Это фича. Сказал отрубить руки, значит хочешь отрубить руки. А если серьёзно, фиг знает. В MSVC есть такое предупреждение.

red75prim ★★★
()
Ответ на: комментарий от red75prim

Это фича. Сказал отрубить руки, значит хочешь отрубить руки.

:)))) Ну за это мы его и любим. :)))

А если серьёзно, фиг знает. В MSVC есть такое предупреждение.

Нет, змий-искуситель, не утащишь ты меня на венду!

dimgel ★★★★★
() автор топика
Ответ на: комментарий от dimgel

Вообще не понятно к чему ты плюсы тут привел. То что в пример приводишь даже близко не похоже на то, что делает impl Trait. В С++20 только вот концепты завезли, которые уже как-то похожи. https://en.cppreference.com/w/cpp/language/constraints вот.

anonymous
()
Ответ на: комментарий от dimgel

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

есть ключевое слово explicit. Но да всё самому.

https://gcc.godbolt.org/z/aj9zYf76T

fsb4000 ★★★★★
()
Ответ на: комментарий от anonymous

То что в пример приводишь даже близко не похоже на то, что делает impl Trait.

Если я правильно тебя понял, он делает передачу по значению + специализацию сгенерированного кода на каждый конкретный тип параметра. (На плюсах то же самое делается добрыми старыми шаблонами. И по ссылке/по значению/на куче/в стеке – вполне понятно из контекста: если unique_ptr<> значит в куче и рантайм полиморфизм бесплатен т.к. мы и так в куче. И никаких impl не надо.)

Плюс какие-то заморочки с выводом типов, на которые ты мне упорно указываешь и которых я не понимаю т.к. ещё не добрался. (Если же речь просто про constraints через концепты по твоей ссылке, то да, есть такое. А про многословность – ну, не может же раст быть ВО ВСЁМ хуже плюсов? :))) )

dimgel ★★★★★
() автор топика
Последнее исправление: dimgel (всего исправлений: 1)
Ответ на: комментарий от fsb4000

говорю же, auto меняем на impl, тоже 4 буквы :)

Так, со второго раза до меня дошло, что в старом синтаксисе плюсовых шаблонов нету lower/upper bound на типопараметры, а есть только enable_if. Жуть какая. Видимо со скалой перемешалось (я пока книжку читал, сразу с несколькими известными мне языками сравнивал – e.g. те же traits со скаловскими).

Спасибо!

dimgel ★★★★★
() автор топика
Последнее исправление: dimgel (всего исправлений: 1)

Ну и вдогонку, чтобы дважды не вставать: «we can’t implement external traits on external types» – это не просто дичь, а просто дичь.

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

Если бы такое было разрешено, был бы ад. Вот ты в своём коде имплементировал чужой трейт Bar для чужого типа Foo. И подключил другой крейт, в котором есть имплементация трейта Baz для трейта Bar. Должен ли теперь для типа Foo быть имплементирован трейт Baz? Вероятно да, ведь ты зачем-то добавлял Bar для Foo и подключал крейт. Но тогда возникает дичь, ведь у Foo может быть другой трейт для которого в свою очередь Baz тоже определён и получился бы конфликт. Т.е. ситуация, полностью рабочий код, ты подключаешь крейт или даже просто обновляешь его и всё перестаёт компилироваться, причём никто не виноват, авторы всех крейтов писали правильно. Если же представить, что о том, что для Foo определён Bar знает только твой код, но не код из крейта, тогда ещё большая дичь будет. Скажем ты пишешь свою структуру, имплементишь для неё Bar и получаешь для неё поддержку Baz. Но потом имплементишь Bar для чужого типа и Baz для него не получаешь, идёшь ругаться на форумы.

В общем текущее ограничение наиболее простое, понятное и логичное.

khrundel ★★★★
()

UPD2. fn vs fun – зачем отдельный keyword для лямбд?

Лолшто? Ты открыл какие-то доки из 2012го года? Какой еще fun? синтаксис лямбд это |a, b| {a + b}.

Trait – не first class тип? Это бред, но даже если так: и что? Или может возможен конфликт одинаковых имён структуры и trait? Короче, смахивает на синтаксический оверхед.

Не осилил трейты и начал спорить с компилятором?

Without the rule, two crates could implement the same trait for the same type, and Rust wouldn’t know which implementation to use

Бедааа. Это же придется zero waste абстракцию написать над внешнем типом в виде целой одной структуры.

чтобы дважды не вставать

Лучше бы вообще не вставал

Aswed ★★★★★
()
Ответ на: комментарий от khrundel

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

Haskell. Хотя это называется orphan instance, считается говном (правильно) и компилятор выдаёт предупреждение. Обычно гораздо правильнее оказывается обернуть в newtype и уже для него реализовывать. Performance hit от этого нулевой.

Miguel ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.