LINUX.ORG.RU

Rust Alpha

 


1

3

Не следил активно за изменениями, но в своё время прочёл уже устаревшую Rust by Example и туториалы с официального сайта. Теперь вот начал неспешно читать новую «книгу», которая заменила собой старые гайды. Но некоторые моменты вызывают удивление. Знаю, что тут есть пару человек, которые в языке неплохо разбираются, так что может обьяснят почему сделали именно так.

1. Функции высшего порядка. Старый вариант:

fn test(f: |int| -> int) -> int
Новый:
fn test<F: Fn(i32) -> i32>(f: F) -> i32
Второй вариант, имхо, приближается к шаблонам С++ (в плохом смысле). Но ок, первая мысль - «подумали над уменьшением многословности для функций с одинаковыми сигнатурами». Не самый частый случай, но возможно, смысл есть. Потом читаю дальше:
fn test<F, G>(x: i32, f: F, g: G) -> i32
    where F: Fn(i32) -> i32, G: Fn(i32) -> i32 {
}
И объяснение:

That is because in Rust each closure has its own unique type. So, not only do closures with different signatures have different types, but different closures with the same signature have different types, as well!

По моему, они взяли худшее из обоих вариантов. Есть в этом какой-то смысл?

Опять же where тут, в отличии от хаскеля, выглядит коряво. Мы и так сначала заявляем, что вот у нас есть типы (<F, G>), потом ещё раз про них говорим (f: F, g: G) и наконец-то определяем их. Я бы ещё понял, если бы where позволяло отказаться от указания типов в угловых скобках.

2. Closures.

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

3. Named arguments, variable arguments.

В описании методов присутствует следующее:

Rust doesn't have method overloading, named arguments, or variable arguments. We employ the builder pattern instead.

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

tailgunner ozkriff numas13

★★★★★

Ответ на: комментарий от quantum-troll

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

Анонимус, кажется, помог мне понять. Дело в том, что в С++ нельзя (нормальными способами) сделать шаблонный параметр «функцию» с определённой сигнатурой. Можно или указатель на функцию, но у него есть ограничения (замыкание не передать) или «просто шаблонный параметр» без ограничения на сигнатуру или «такой же» объект как в расте Fn... - std::function.

Всем спасибо за объяснения.

DarkEld3r ★★★★★
() автор топика
Ответ на: комментарий от DarkEld3r
void call( std::function<void (std::function<int (int, int)>)> func);

типа так? функция, которая принимает функцию, которая принимает 2 параметра int и возвращает int.

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

типа так? функция, которая принимает функцию, которая принимает 2 параметра int и возвращает int.

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

fn call(func: &mut Fn(&mut Fn(i32, i32) -> i32) -> i32)

Но в расте можно и через шаблоны (дженерики):

fn call<F: Fn(&mut Fn(i32, i32) -> i32) -> i32>(func: F)
А вот так на С++ уже нельзя. Только вот так:
template<typename T>
void call(T t);

DarkEld3r ★★★★★
() автор топика
Ответ на: комментарий от DarkEld3r
fn call<F: Fn(&mut Fn(i32, i32) -> i32) -> i32>(func: F)

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

Чем эта запись отличается от того, что на С++?

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

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

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

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

Да.

Чем эта запись отличается от того, что на С++?

Какая именно? Твоя? Тогда quantum-troll всё правильно сказал. Если же вариант с call(T t), то тем, что мы не можем наложить никаких ограничений, кроме тех, которые связаны с использованием параметра.

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

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

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

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

fn call<F: Fn()>(f: F) {
    f();
}

fn main() {
    call(||{ println!("eeee"); 10 });
}

DarkEld3r ★★★★★
() автор топика
Ответ на: комментарий от quantum-troll

Вопросительный знак забыл, да.

Ну поставь точку с запятой после числа 10, и всё.

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

Вот так почти работает, но будет проблема если функция ничего не возвращает:

fn call<F: Fn() -> T, T>(f: F) {
    f();
}

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

... Вот так почти работает, но будет проблема если функция ничего не возвращает: ...

А что у тебя так не работает? Если функция ничего не возвращает, то она возвращает тип ():

fn call<T, F: Fn() -> T>(f: F) { f(); }

fn main() {
    call(||       { println!("1.1"); 0u8});
    call(|| -> u8 { println!("1.2"); 0u8});
    call(||       { println!("2.1"); });
    call(|| -> () { println!("2.2"); });
}
1.1
1.2
2.1
2.2
Program ended.

http://is.gd/0V6ggo

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

Хм. Интересно как, в таком случае, println влияет и во что выводит.

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

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

Ты, может, об этом? Правда, там я говорил не про разыменование ссылок, а про другую механику передачи параметров (добавление «&»).

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

Хз, какая-то магия компилятора.

Магия магией, но ведь компилятор же можно просто взять и спросить:

% rustc hello.rs -Z unstable-options --pretty=expanded
#![feature(no_std)]
#![no_std]
#[prelude_import]
use std::prelude::v1::*;
#[macro_use]
extern crate "std" as std;
fn main() {
    ::std::old_io::stdio::println_args(::std::fmt::Arguments::new_v1({
                                                                         static __STATIC_FMTSTR:
                                                                                &'static [&'static str]
                                                                                =
                                                                             &["Hello, world!"];
                                                                         __STATIC_FMTSTR
                                                                     },
                                                                     &match ()
                                                                          {
                                                                          ()
                                                                          =>
                                                                          [],
                                                                      }))
}

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

Магия магией, но ведь компилятор же можно просто взять и спросить:

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

ozkriff
()
Ответ на: комментарий от quantum-troll

Это вообще про любую документацию ржавчины можно сказать, даже про официальные доки) В такой ситуации я бы «устаревшим» называл чего-то для rust 0.11-0.12 и ниже, а над rust-by-example вполне себе люди работают потихоньку, обновляют-дополняют: https://github.com/rust-lang/rust-by-example/network.

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

Если функция ничего не возвращает, то она возвращает тип ():

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

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

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

ты явно указал возвращаемый тип у замыканий просто для иллюстрации

ага

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

Устаревшую?

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

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

Ещё один дурацкий вопрос по FFI. В разделе «Accessing foreign globals» говорится, что переменные надо объявлять как mut, если хочется изменять их из растового кода. Правильно я понимаю, что если переменная может изменяться в чужом коде, но сам я её изменять не собираюсь, то mut не нужен? Просто как-то не очень логично получается.

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

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

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

Ещё один дурацкий вопрос по FFI. В разделе «Accessing foreign globals» говорится, что переменные надо объявлять как mut, если хочется изменять их из растового кода. Правильно я понимаю, что если переменная может изменяться в чужом коде, но сам я её изменять не собираюсь, то mut не нужен?

Да, вроде так.

Просто как-то не очень логично получается.

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

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

А как было бы логичней?

С моей точки зрения, иммутабельность гарантирует, что «переменная» не изменится никогда. По идее, это позволяет делать дополнительные оптимизации. А тут мы «обманываем компилятор». В D изменение иммутабельных значений грозит UB, как и в С++ константных. Или можно плюсовый volatile вспомнить.

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

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

Почему?

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

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

Так обычные статики, которые не в ffi, а просто так, тоже могут иметь «внутреннюю изменяемость» (interior mutability). Для этого они и нужны ж, вроде. Если хочешь, что бы оно точно-точно не изменялось, то надо не static, а const.

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

Обсуждение^W Срач по поводу внутренней изменяемости в итоге и вырос в чертов мутпоклипсис же (google «rust mutpocalypse», если что).

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

Так обычные статики, которые не в ffi, а просто так, тоже могут иметь «внутреннюю изменяемость» (interior mutability)

Мне казалось, что «interior mutability» ортогональна статикам. В смысле, std::cell позволяет иметь внутреннее изменяемое состояние «обычным типам» (не статикам)?

Если хочешь, что бы оно точно-точно не изменялось, то надо не static, а const.

Про const в расте, вроде, ничего и не видел ещё.

Ну и пока что сбит с толку. Строковые литералы ведь имеют тип «&'static str» - казалось бы куда ещё константнее. Хотя я так понимаю, что static - это больше про «время жизни».

Ссылку почитаю, там и про константы есть, но похоже надо немного всё переосмыслить. Ну и насколько я вижу, константы накладывают много ограничений. Вообще, есть ли смысл их прописывать «везде» (где можно)?

Просто раньше мне всё казалось проще: иммутабельность по умолчанию (и это здорово), если надо - пишешь mut. «Внутренняя мутабельность», после С++, вообще не смущает, необходимость вполне понятна. Ну и статики как раз иммутабельны, по умолчанию, как и всё остальное. Более того, если добавить mut, то будет «use of mutable static requires unsafe function or block».

А возможность «неявной модификации» «иммутабельных» простых типов разрывает шаблон.

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

Кстати, можешь рассказать в двух словах, что там с переполнением решили? Ну или научи как из вот такого информацию извлекать. Написано «A simplified version of Gabhor's „Scoped Attributes for Integer Overflow“ RFC». В самом предложении описано несколько возможных реализаций. Как понять на чём в итоге остановились? Только смотреть код?

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

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

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

Точнее в детали я не вчитывался. Подробнее буду разбираться, когда вмержат. Тогда еще и пост какой-нибудь с разъяснениями замутят, как обычно с крупными изменениями).

Ну или научи как из вот такого информацию извлекать. Написано «A simplified version of Gabhor's „Scoped Attributes for Integer Overflow“ RFC». В самом предложении описано несколько возможных реализаций. Как понять на чём в итоге остановились?

Дык это, там в Summary описано коротко, потом подробнее и в самом конце только про альтернативы говорится.

Только смотреть код?

Это никогда не помешает), но код жеж еще не вмержили, может он поменяется.

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

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

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

Мне нравится как в C# сделано с их checked/unchecked. Разве что умолчание другое выбрал бы.

Дык это, там в Summary описано коротко, потом подробнее и в самом конце только про альтернативы говорится.

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

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

... Мне казалось, что «interior mutability» ортогональна статикам. В смысле, std::cell позволяет иметь внутреннее изменяемое состояние «обычным типам» (не статикам)?

Ага, это Скорее unsafe блоки это позволяют. Аналог cell`ов же не сложно написать.

... Строковые литералы ведь имеют тип «&'static str» - казалось бы куда ещё константнее. Хотя я так понимаю, что static - это больше про «время жизни». ...

Да, static - это что объект будет жить от начала выполнения программы (до main) и до самого завершения (после main).

Ну и насколько я вижу, константы накладывают много ограничений.

По сравнению со статиками - не намного больше.

есть ли смысл их прописывать «везде» (где можно)?

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

Просто раньше мне всё казалось проще ... А возможность «неявной модификации» «иммутабельных» простых типов разрывает шаблон.

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

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

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

Видимо, решили что накладные расходы великоваты.

Мне нравится как в C# сделано с их checked/unchecked. Разве что умолчание другое выбрал бы.

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

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

Там же просто дана ссылка на прошлый вариант RFC, который был сложнее. Старый обсудили и решили переделать-упростить. Результатом стал новый RFC, который в этом PR и добавляется и на отрендеренную версию которого ведет ссылка «Rendered version»). Ну и делать будут как в нем написано.

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

А в принятом rfc - просто ключ компилятора.

Дык, в С# как раз «вариант по умолчанию» задаётся ключом компилятора. Но можно явно затребовать проверять переполнение или наоборот игнорировать. Правда мне это как раз не нравится в том смысле, что недостаточно видеть код, чтобы судить о поведении.

С усложнением синтаксиса тоже не согласен. Если бы придумали как-то особенно помечать каждую операцию, то да. А так мы просто пишем:

checked
{
    // Тут будет проверка переполнения.
}
Всего-то одно ключевое слово и никаких «спец-символов», которых так боятся хейтеры.

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

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

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

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

Видел. Попробуй в главном канале ирки спросить, там народ не так стесняется)

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

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

Кстати, const и mut для сырых указателей в ffi тебя не беспокоят?

В смысле, «*const T» может поменяться? Ну да, в гайде по FFI говорится:

have no guarantees about aliasing or mutability other than mutation not being allowed directly through a *const T.

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

Кстати, мне кажется очень удачным, что сырые указатели называются иначе. В смысле не просто «*Т», а «*const T». То есть нам даже синтаксис намекает, что это другая сущность.

А про иммутабельные сущности явно говорится, что менять их «нельзя» (UB) даже в ансейф блоке. И это кажется вполне логичным. Поэтому если окажется, что FFI (extern) подчиняется другим правилам, то такое решение не кажется мне идеальным.

Попробуй в главном канале ирки спросить, там народ не так стесняется)

Если на реддите так и не ответят, то придётся.

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

Кстати, про диапазоны ещё поною. В цикле for можно использовать ".." (без верхней границы), "..." - нельзя. В матче наоборот - есть "..." (включая верхнюю границу), но ".." - нет. Плюс две точки используется, если нам не интересно значение - Some(..). Теперь добавили ещё две точки для std::ops::RangeFull.

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

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

Хм, фак, это я пропустил. Спасибо, надо будет пару мест переписать, на всякий.

такое решение не кажется мне идеальным

Ну, с учетом вышесказанного, может и правда кривовато.

Кстати, про диапазоны ещё поною

Тут, боюсь, помогу не лучше гугла - я диапазоны особо не использовал еще.

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

я тоже поною. Это жуткая гадость. пару раз уже наткнулся на ситуацию, когда нужно было без верхней границы, и вышло наоборот. ПОТОМУ ЧТО, ЛЯТЬ, .. и ... не цепляет сразу глаз разницы!! Кто это вообще, лять, придумал, лять?

Теперь добавили ещё две точки для std::ops::RangeFull

Молодцы, чо. Теперь логические ошибки делать еще проще! Даешь еще ....!

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