Fri, 02 Mar 2018 15:36:45 +0300Fri, 02 Mar 2018 15:36:45 +0300Ти́ повые состояния (`typestates`) in Rust Мой вольный авторский перевод заинтересовавшего меня поста, который я прочитал, просматривая свежий выпуск \"Неделя в Rust\". Оригинал можно найти здесь. Но лучше прочитать мой перевод.
Давным давно, Rust был языком с типовыми состояниями. Официально типовые состояния были выкинуты задолго до Rust 1.0. В этом тексте я раскрою вам секретный секрет сообщества Rust: в Rust по-прежнему есть типовые состояния. БА-ДУМ-ТССС!
Секундочку, а что такое \"типовое состояние\"?
Рассмотрим объект, представляющий файл — давайте обозначим его структурой данных MyFile. До того момента, как MyFile будет открыт, он не может быть прочитан. В случае если MyFile закрыт, он также не может быть прочитан. Между двумя этими состояниями файл может быть прочитан. Типовые состояния это механизм, дающий тайпчекеру возможность проверить, что вы не делаете следующих ошибок:
fn read_contents_of_file(path: &Path) -> String { let mut my_file = MyFile::new(path); my_file.open(); // Ошибка: эта операция может завершиться неудачно. let result = my_file.read_all(); // Невалидно, если `my_file.open()` завершилось неудачно. my_file.close(); my_file.seek(Seek::Start); // Ошибка: `my_file` уже закрыт. result }
В этом примере мы сделали две ошибки: 1. мы прочитали файл, который может не быть открытым; 2. мы установили текущую позицию чтения в файле, который уже был закрыт.
В большинстве языков программирования мы легко можем спроектировать API MyFile так, чтобы быть уверенными, что первая ошибка будет невозможной, просто бросая исключение, когда файл не получается открыть. Некоторые стандартные библиотеки и рамки решают не следовать этому принципу ради гибкости, но возможности существуют в самом языке.
Вторую ошибку намного сложнее перехватить. Большинство ЯП поддерживают необходимые возможности, чтобы сделать эту ошибку сложной для воспроизведения, как правило, закрывая файл при уничтожении или в конце вызова функции высокого порядка, но единственный неакадемический язык, о котором я знаю, который может фактически полностью предотвратить эту ошибку, это Rust.
Тривиальные типовые состояния в Rust
Итак, как мы делаем это в Rust?
Простейший способ сделать это — ввести здоровые типы (ради Бога, если знаете более приличный перевод термина \"sane types\", сообщите мне) для наших операций над MyFile:
impl MyFile { // `open` это единственный способ создания экземпляра `MyFile`. pub fn open(path: &Path) -> Result { ... }
// `seek` требует экземпляр `MyFile`. pub fn seek(&mut self, pos: Seek) -> Result { ... }
// `read_all` требует экземпляр `MyFile`. pub fn read_all(&mut self) -> Result { ... }
// `close` принимает аргумент `self`, не ссылки `&self` или `&mut self`, // который означает, что этот метод *перемещает* свой объект, эффективно поглощая его. pub fn close(self) -> Result { ... } }
impl Drop for MyFile { // Определяем деструктор для автоматического закрытия экземпляра `MyFile`, // если мы еще не закрыли его. fn drop(&mut self) { ... } }
Давайте перепишем наш изначальный пример:
fn read_contents_of_file(path: &Path) -> Result { let mut my_file = MyFile::open(path)?; // Обратите внимание на `?`. Это простой оператор, который просит // компилятор проверить успешность выполнения операции. // *Единственный* способ получить экземпляр `MyFile` // это иметь успешно завершенным `MyFile::open`.
// В этой строке `my_file` это объект `MyFile`, // который можно использовать.
let result = my_file.read_all()?; // Корректно. my_file.close(); // Корректно.
// Поскольку `my_file.close()` поглощает `my_file`, // эта переменная больше не существут.
my_file.seek(Seek::Start)?; // Ошибка, которую обнаружит компилятор. result }
Это работает и в более сложных случаях:
fn read_contents_of_file(path: &Path) -> Result { // Аналогично. let mut my_file = MyFile::open(path)?; let result = my_file.read_all()?; // Корректно.
if are_we_happy_yet() { my_file.close(); // Корректно. }
// Поскольку `my_file.close()` поглощает `my_file`, // эта переменная больше не существует, по крайней мере, // в данной ветви выполнения кода.
my_file.seek(Seek::Start)?; // Ошибка, которую обнаружит компилятор. result
// Если мы не закрыли `my_file`, то по выходе из блока кода, // в котором мы его определили (в данном случае, это тело функции), // здесь сработает его деструктор, который закроет файл. }
Ключевой момент заключается в том, что система типов Rust обеспечивает соблюдение того факта, что переменная не может быть использована после перемещения. В нашем случае my_file.close() переместил значение, эффективно поглотив его.
Даже если бы мы попытались скрыть переменную где-то в другом месте и повторно использовать её после вызова my_file.close(), эта попытка была бы пресечена компилятором:
fn read_contents_of_file(path: &Path) -> Result { // Аналогично. let mut my_file = MyFile::open(path)?; let result = my_file.read_all()?;
let mut my_file_sneaky_backup = my_file; // Здесь мы переместили `my_file` в `my_file_sneaky_backup`. // Поэтому мы не можем больше использовать.
my_file.close(); // Ошибка, которую обнаружит компилятор.
my_file_sneaky_backup.seek(Seek::Start)?; result
// Если мы не закрыли `my_file`, // здесь сработает его деструктор, который закроет файл. }
Давайте испробуем иной путь, чтобы обмануть компилятор, для доступа к файлу после его закрытия:
fn read_contents_of_file(path: &Path) -> Result { let my_shared_file = Rc::new(RefCell::new(MyFile::open(path)?)); // Здесь `my_shared_file` это shared указатель на мутабельный // экземпляр `MyFile`, наподобие обычной // ссылки в Java, C# или Python.
let result = my_shared_file.borrow_mut() .read_all()?; // Корректно.
let my_shared_file_sneaky_backup = my_shared_file.clone(); // Мы клонировали указатель, создав второй способ доступа к `my_shared_file`.
// Давайте удостоверимся, что мы можем использовать бэкап // и оригинальный файл: my_shared_file_sneaky_backup.seek(Seek::Start)?; // Корректно. my_shared_file.seek(Seek::Start)?; // Также корректно.
// Ха-ха, теперь-то мы, конечно, можем закрыть `my_shared_file`, // а потом манипулировать `my_shared_file_sneaky_backup`, // словно мы в Java, C# или Python!
// Да вот беда, мы не можем вызвать `my_shared_file.close()`, // потому что укрытый `MyFile` разделен (shared), // что означает, что никто не может *перемещать* его. my_shared_file.close(); // Ошибка, которую обнаружит компилятор. my_shared_file_sneaky_backup.seek(Seek::Start)?; result
// Если мы не закрыли `my_file`, деструктор закроет его здесь. }
И снова наши поползновения были пресечены компилятором. В самом деле, если явно не перейти в unsafe режим, мы не сможем разбить инвариант, в котором seek не может быть использован после закрытия.
Этот пример демонстрирует первый кирпич типовых состояний в Rust: типизированную операцию перемещения. Но мы разобрались только с тривиальным случаем, в котором файлы имеют лишь два состояния: открыт и закрыт.
Давайте посмотрим, как мы можем управиться с более сложными случаями.
Сложные типовые состояния
Вместо файлов, давайте теперь рассмотрим следующий (и, по общему признанию, довольно тупой) сетевой протокол: 1. Отправитель отправляет сообщение \"HELLO\". 2. Приемник получает сообщение \"HELLO\", отвечает сообщением \"HELLO, YOU\". 3. Отправитель получает сообщение \"HELLO, YOU\", отвечает случайным числом. 4. Получатель получает номер от отправителя, отвечает тем же номером. 5. Отправитель получает одинаковое количество от приемника, отвечает \"BYE\". 6. Получатель получает \"BYE\" от отправителя, отвечает \"BYE, YOU\". 7. Вернитесь к 1.
Любое другое сообщение игнорируется.
Мы можем спроектировать нашего Отправителя (и аналогично Получателя) так, чтобы гарантировать, что операции выполняются в правильном порядке. На данный момент мы не заботимся об идентификации корреспондента или числа.
Для этой цели мы объединим типовые состояния с другим методом, хорошо известным функциональным программистам, таким как фантомные типы.
// Серия типов нулевого размера (нет полей => нет состояния => в рантайме не существуют), // представляющих состояние отправителя. // Значение не имеет значения, только тип // (отсюда и название \"фантомный тип\"). struct SenderReadyToSendHello; struct SenderHasSentHello; struct SenderHasSentNumber; struct SenderHasReceivedNumber;
/// Поле нулевого размера, не существующее во время выполнения. state: S; }
/// Следующие методы могут быть вызваны независимо от состояния. impl Sender { /// Порт, используемый для подключения отправителя. fn port(&self) -> usize;
/// Закрыть отправителя, раз и навсегда. fn close(self); }
/// Следующий метод может быть вызван только в состоянии SenderReadyToSendHello. impl Sender { /// Отправить приветствие. /// /// Этот метод поглощает отправителя в его текущем состоянии /// и возвращает его в новом состоянии. fn send_hello(mut self) -> Sender { self.inner.send_message(\"HELLO\"); Sender { /// Перемещаем реализацию сетевого I/O. /// Компилятор достаточно умён, /// чтобы понять, что это не требует никакой рантайм-операции. inner: self.inner,
/// Заменяем поле нулевого размера. /// Эта операция будет вычищена во время выполнения. state: SenderHasSentHello } } }
/// Следующий метод может быть вызван только в состоянии SenderHasSentHello. impl Sender { /// Ждать, пока получатель не отправит \"HELLO, YOU\", /// ответить числом. /// /// Вернуть отправитель в состоянии `SenderHasSentNumber` fn wait_respond_to_hello_you(mut self) -> Sender { // ... }
/// Если получатель отправляет \"HELLO, YOU\", ответить числом и /// вернуть отправитель в состоянии `SenderHasSentNumber`. /// /// Иначе вернуть неизмененное состояние. fn try_respond_to_hello_you(mut self) -> Result { // ... } }
/// Следующий метод может быть вызван только в состоянии SenderHasSentNumber. impl Sender { /// Ждать, пока получатель не отправит число, ответить \"BYE\". /// /// Вернуть отправитель в состоянии `SenderReadyToSendHello` fn wait_respond_to_hello_you(mut self) -> Sender { // ... }
/// Если получатель отправляет число, ответить и вернуть отправитель /// в состоянии `SenderReadyToSendHello`. /// /// Иначе вернуть неизмененное состояние. fn try_respond_to_hello_you(mut self) -> Result { // ... } }
Из этого дизайна очевидно, что Отправитель может следовать только такому протоколу:
1. из шага 1 (SenderReadyToSendHello) он может идти только к шагу 3; 2. из шага 3 (SenderHasSentHello) он может только либо оставаться на шаге 3 или идти к шагу 5; 3. из шага 5 (SenderHasSentNumber) он может только либо оставаться на шаге 5 или идти к шагу 1.
Любая попытка нарушить протокол будет заблокирована системой типов.
Если вам когда-нибудь понадобится работать с сетевыми протоколами, драйверами устройств, промышленными устройствами со специфичными инструкциями безопасности или OpenGL/DirectX/чем угодно еще, что потребует от вас выполнения сложных взаимодействий с \"железом\", вы определенно будете счастливы иметь такие гарантии!
Добро пожаловать в мир типовых состояний.
Краткое примечание: за типовые состояния
Продолжая наш сетевой пример, что если мы захотим, скажем, сохранять число, отправленное сервером, чтобы проверить, что означает ответ? Чтобы сделать это, мы можем сохранять число в SenderHasSentNumber:
struct SenderHasSentNumber { number_sent: u32, }
И снова, компилятор будет проверять, что код получает доступ к number_sent, только когда отправитель находится в состоянии SenderHasSentNumber.
Мы потеряем (крошечный) объем производительности, компилятор не сможем оптимизировать трансформацию Отправителя между идентичными представлениями, но вообще это того стоит.Virtuos86
Ти́ повые состояния (`typestates`) in RustМой вольный авторский перевод заинтересовавшего меня поста, который я прочитал, просматривая свежий [...]
читать описание
Tue, 05 Dec 2017 04:24:52 +0300Tue, 05 Dec 2017 04:24:52 +0300Когда революционная версия 57 еще пребывала в статусе beta, а я начинал делать адд-оны к \\\"огнелису\\\", я столкнулся с тем, что зачастую в примечаниях к документации о том или ином параметре встречается оговорка \\\"скопировано с Chrome Web Extensions API\\\". Но здесь нужно отметить коренное отличие, даже два. Во-первых, в огнелисе для доступа к API используется корневой объект `browser`, в то время как Chrome в качестве такового использует `chrome`. Во-вторых, несмотря на идентичное API, существует разница и в способе его использования: в Хроме нужно передавать в функции API (методы chrome) колбэки, что провоцирует COP — колбэк-ориентированное программирование, в то время как Файерфоксе функции API возвращают промисы, что, на мой взгляд, гораздо удобнее. Когда я обнаружил эти отличия, я немного приуныл, поскольку это означало, что при портировании дополнения между браузерами его либо придется переписывать самым обезьянним образом, либо изначально городить диспатчинг. Но когда зарелизился FF 57, я в ходе разработки обнаружил, что в Файерфоксе заработал и объект `chrome`. К сожалению, я не проверил, является ли он обычным алиасом к `browser` или он копирует поведение хромовского оригинала и позволяет работать с ним с использованием колбэков, а не промисов. Если это так, то это бы значительно упростило портирование дополнений с Хрома в Файерфокс.
Пустяковое замечание, но для истории полезно.Virtuos86
Когда революционная версия 57 еще пребывала в статусе beta, а я начинал делать адд-оны к \\\"огнелису\\\", я столкнулся с тем, что зачастую в примечаниях [...]
читать описание
Mon, 13 Nov 2017 12:13:13 +0300Mon, 13 Nov 2017 12:13:13 +0300Как я и обещал — а я всегда держу обещания, когда мне это выгодно — пишу о том, что моими растовыми декораторами пользоваться можно, но осторожно. Хотя я и починил с помощью них свою игру, о чем и поведал в предыдущем триумфальном посте, но тогда же и обнаружил очередную прореху. Но я мудр не по годам, поэтому давно понял, что мою хорошую идею нужно оформить в более совершенную форму, а именно переписать по уму библиотеку целиком. и я обратился за помощью к высшим силам, и тогда моё внимание обратили на существование либы под названием \"syn\", то есть \"syntax\", как я полагаю. Предназначен syn именно для парсинга исходного кода на Rust. Вот так раз. Как раз что мне нужно. К сожалению, он у меня почему-то установился криво, потому что половина функций не импортируется, когда я попытался создать тестовый проект. Разбираться мне было некогда, но понятно, что когда я захочу переписать свою библиотечку декораторов, этим придется заняться. Пока же я переключаюсь на игру.Virtuos86
Как я и обещал — а я всегда держу обещания, когда мне это выгодно — пишу о том, что моими растовыми декораторами пользоваться можно, но осторожно. [...]
читать описание
Mon, 13 Nov 2017 11:51:46 +0300Mon, 13 Nov 2017 11:51:46 +0300У меня есть две новости, плохая и хорошая. Но поскольку вас их эмоциональная окраска не касается, то начну с любой. Я нашел причину падений своей игры. А вот и виновник торжества: pub fn GenerateMonsters() { let mut v = vec!(); for mt in MonsterTypes.iter() { if (mt.Level == unsafe {map::CUR_MAP as u32}) && map::random(0, 6) == 0 { // проблемная строчка v.push(*mt); }; } for i in 0..MaxMonsters - 1 { let mut m: TMonster = v; let (x, y) = map::FreeMapPoint(get_ref_curmap!()); m.x = x; m.y = y; unsafe { MONSTERS = m; } } } Как говорится в одном старом анекдоте, \"на третий день томящийся в плену индеец Зоркий Глаз заметил, что в сарае, куда его заперли, нет одной стены\". Суть в том, что условие \"map::random(0, 6) == 0\" с вероятностью 5/6 не выполнится ни для одного \"mt\", а значит массив \"v\" останется пустым. А потом я передаю в функцию по сути два нуля, потому что длина пустого массива равна нулю: \"map::random(0, v.len())\" Функция \"random\" определена так: pub fn random(start: usize, end: usize) -> usize { use rand::{thread_rng, sample}; if endVirtuos86
У меня есть две новости, плохая и хорошая. Но поскольку вас их эмоциональная окраска не касается, то начну с любой.Я нашел причину падений [...]
читать описание
Mon, 13 Nov 2017 10:40:11 +0300Mon, 13 Nov 2017 10:40:11 +0300Вот. Одна беда — мне надо для ST3, а для него темы это целые пакеты с ресурсами, а здесь можно скачать только голый XML-документ. Зато реально удобно.Virtuos86
Вот. Одна беда — мне надо для ST3, а для него темы это целые пакеты с ресурсами, а здесь можно скачать только голый XML-документ. Зато реально [...]
читать описание
Sun, 12 Nov 2017 14:11:19 +0300Sun, 12 Nov 2017 14:11:19 +0300Здравствуйте.
Я пишу код на Rust, используя некогда хипстерский редактор Sublime Text, в данный момент плавно переполз на 3-ю версию со 2-ой. Теперь на звание хипстерских поделок претендуют поделки на базе Electron типа VSCode и Atom, а юзеры "саблайма" благополучно перешли в разряд степенных, не бегущих за меняющейся модой серьезных людей .
Работать с Rust кодом помогает плагин `Rust Enhanced`. Помимо надоедливой проверки корректности кода, которая поглощает мои нервные клетки с аппетитом голодной гиены — потому что я обычный быдлокодер, а Rust — очень сложный для нашей братии, — так вот, помимо этого плагин предоставляет несколько режимов для сборки готового кода, от обычной компиляции до запуска тестов/бенчмарков/сборки документации/приготовления кофе/и чего-то ещё.
Но! Но я столкнулся с тем, что при неудачной сборке получаю слишком мало деталей и подробностей. По счастью, `rustc` очень дружелюбный и посоветовал мне использовать ключ `--verbose` для сборки.
Вывод ясен — нужен свой сборочный сценарий. Сказано — сделано.
Выбираем в панели меню пункт `Tools`. Далее `Build System` -> `New Build System...`
Открывается новая вкладка редактора с заготовкой:
{
"shell_cmd": "make"
}
Изменяем на:
{
"shell_cmd": "RUST_BACKTRACE=1 cargo run --verbose"
}
Дальше сохраняем:
Теперь идем по знакомому пути `Tools` -> `Build System` и выбираем свою систему сборки вместо `RustEnhanced`. Теперь в случае проблемной компиляции можно будет увидеть необходимые подробности.
Здравствуйте.
Я пишу код на Rust, используя некогда хипстерский редактор Sublime Text, в данный момент плавно переполз на 3-ю версию со 2-ой. Теперь [...]
читать описание
Tue, 07 Nov 2017 14:09:14 +0300Tue, 07 Nov 2017 14:09:14 +0300У меня наметилась лишняя неделя безработицы недавно, поэтому устав пялиться в Веб 2.0, я внезапно вспомнил, что программист. Открыл проект игры-"рогалика" на великом и могучем языке программирования Rust, с которым я еще месяц назад носился, как с писанной торбой (с проектом, в смысле), но отложил по паре причин. Из которых первой является то досадное обстоятельство, что библиотека "cursive", которую я выбрал для реализации TUI (консольного интерфейса) игры, не предоставляет на данный момент возможностей раскрашивать произвольные участки текста в текстовых лайотах произвольным же образом. Только моноцвет. А как я по-вашему должен разнообразить вид стены символьных закорючек, в которых пылкое воображение игромана должно разглядеть героев и чудовищ, оружие и злато, степи, стены и леса? Никак.
Вторым же и куда более неприятным затруднением явилась регрессия, наметившаяся в кодовой базе. А если просто, по пацаночке, то игра стала падать при определенных действиях. А значит, надо дебажить.
В общем, долго ли, коротко, а несколько дней назад я открыл проект, окинул горделивым взором килобайты вершины погромистской деятельности своей, потом переключился в "полуночный командир", которым я по недоразумению пользуюсь как файловым менеджером на линуксе, запустил свою игрулю и убедился, что она падает столь же исправно.
Погрустив с минутку, я было ринулся расставлять println'ы aka отладочную печать, а потом подумал, что было бы неплохо использовать такую штуку из Python мира, как декораторы, для облегчения своей доли. То бишь повесил на нужную функцию декоратор, реализующий отладочную печать, скажем, при установленном флаге "DEBUG = true;" при вызове обернутой им функции, и всё, не надо добавлять строчки с принтами, а потом их удалять, когда будут не нужны. К тому же Rust это язык со статической типизацией, а значит, можно сделать так, чтобы декораторы навешивались при включенном флаге отладки во время компилирования, то есть с минимальным оверхедом.
Ребят, если бы я знал, в какую жопу лезу, я бы передумал. Вот я отвечаю.
Но, но, но. Я всё-таки это сделал и сделал, надеюсь хорошо. Поэтому горд неимоверно. И ничего страшного, что на Python декораторы реализуются за 5 минут, просто потому что они уже фактически доступны искаропки, благодаря возможностям языка, а я потратил 5 дней, сражаясь аки лев с конпелятором Rust, и исписав двести строк программного кода (в основном там разбор AST определения функции).
Конечно, пока реализованы не все возможности питоньих декораторов, например, декораторы с добавочными аргументами или декораторы структур и типажей (хотя тут подозреваю, я надорвусь, если возьмусь делать). Но основа: взять функцию и подменить ее обёрткой, — сделана, и этим можно пользоваться. А вот и ссылка на репозиторий: https://github.com/Virtuos86/rust-decorators
А теперь можно и приступить к отладке "рогалика". О боже, дай мне сил…Virtuos86
У меня наметилась лишняя неделя безработицы недавно, поэтому устав пялиться в Веб 2.0, я внезапно вспомнил, что программист. Открыл проект [...]
читать описание
Mon, 16 Oct 2017 09:57:09 +0300Mon, 16 Oct 2017 09:57:09 +0300
Я на время оставил Rust и погрузился в таинство создания Web Extensions под новые версии Firefox. Как известно (мне), Mozilla собралась закопать XUL и старого Файерфокса скоро не станет. Вместе с этим перестанут работать тонны аддонов, которыми, собственно, и был славен этот браузер. Кто-то уже приготовился закапывать "лису", прогнозируя потерю пользовательской базы, но проблема в том, что адекватной замены старым возможностям кастомизируемости среди других популярных браузеров нет, поэтому и бежать особо некуда. Я же бегать не люблю и уже имею в своем активе 2 простеньких (по возможностям, а не по усилиям затраченным на их написание и особенно отладку) дополнения, и одно портированное с Chrome дополнение, которое добавляет определенные приятности в UX. Все эти дополнения предназначены для работы на сайте linux.org.ru, LOR, "информационного ресурса об операционной системе Linux в России".
Что я могу сказать о своих впечатлениях?
Во-первых, оказалось, что когда всю жизнь пишешь синхронный последовательный код, отслеживать и представлять мысленно логику потока исполнения программы, сталкиваясь с асинхронным кодом, не то, чтобы трудно, но приходится ломать шаблоны алгоритмического мышления. Я лично наломал столько шаблонов, что ого-го! Го.
Во-вторых, документация на https://developer.mozilla.org/en-US/Add-ons/WebExtensions достаточно хороша, но некоторые нюансы приходится постигать пошастав по коллекции готовых дополнений, https://github.com/mdn/webextensions-examples (вот она действительно хороша), а некоторые и вовсе после несколькичасового б(з)дения над вроде как рабочим кодом (ну вот, всё по документации делаю, даже код только скопировал и чуть-чуть изменил, что надо-то, а?!). Тем не менее, что хотел, то сделал, а значит я молодец делать дела с Web Extension API можно. Кстати, благодаря тому, что Chrome также поддерживает Web Extension API, как и Opera (строго говоря, в документации для многого прямо указано, что это скопировано с Chrome API), если у вас есть на примете дополнения с Хрома, которые вы бы хотели перетащить в Огнелис, то вероятно это вполне осуществимая затея.Virtuos86
Я на время оставил Rust и погрузился в таинство создания Web Extensions под новые версии Firefox. Как известно (мне), Mozilla собралась закопать XUL и старого [...]
читать описание
Thu, 12 Oct 2017 13:58:02 +0300Thu, 12 Oct 2017 13:58:02 +0300Как для скриптокодера для меня многое в диковинку в практике программирования на ЯП со статической типизацией. Так меня осенило как-то, что переменные, которые используются в основном для индексации векторов и массивов имеет смысл иметь тип \"usize\". До этого я ничтоже сумняшеся инициализировал их дефолтным числовым типом \"i32\". Ну а что, удобно же (в Rust если у числа не указан тип, оно по умолчанию относится к типу \"i32\"). Юмор в том, что \"i32\" это знаковый тип с диапазоном значений от -128 до 127, а индексы в Rust должны быть исключительно положительными. Когда я осознал эту нелепость, я сначала перевел переменные-индексы на использование типа \"u32\", беззнаковых 32-х битных целых. Но здесь вступает другой нюанс. Rust для операции индексации использует значения типа \"usize\". Этот тип платформозависим: на 32-х битных ОС он является аналогом типа \"u32\", на 64-х битных — аналогом типа \"u64\". Это логично, давать возможность использовать максимально большое число для индексации. Rust в операции индексации не приводит автоматически индексы к типу \"usize\", поэтому нужно это делать вручную, что выглядит как:
let v = vec!; println!(\"{}\", v);
Примечание: если в качестве индекса используется числовой литерал без указания типа, то Rust скомпилирует код, но для его корректности тип литерала выведется как \"usize\", хотя, как я уже отметил выше, по дефолту числовые литералы без указания типа относятся к типу \"i32\":
fn main() { let index = 1; // при компиляции эта строка будет, условно говоря заменена на \"let index: usize = 1\" или \"let index = 1usize\" let item = vec!; println!(\"{:?}\", item); }
В результате мой код пестрел \"as usize\"-ами. Заменил \"u32\" на \"usize\", стало хорошо. Ну и да, подозреваю, что используй я \"Clippy\", инструмент проверяющий Rust код на наличие \"вредных привычек\" и глупостей, он вероятно обратил бы мое внимание на этот факт.Virtuos86
Как для скриптокодера для меня многое в диковинку в практике программирования на ЯП со статической типизацией. Так меня осенило как-то, что [...]
читать описание