Язык программирования Rust
Скачать 7.02 Mb.
|
--> src/main.rs:2:13 | 2 | let x = (let y = 6); | ^ ^ | = note: `#[warn(unused_parens)]` on by default help: remove these parentheses | 2 - let x = (let y = 6); 2 + let x = let y = 6; | For more information about this error, try `rustc --explain E0658`. warning: `functions` (bin "functions") generated 1 warning error: could not compile `functions` due to 2 previous errors; 1 warning emitted присваивания возвращает значение присваивания. В таких языках можно писать код x = y = 6 и обе переменные x и y будут иметь одинаковое значение 6 . Но в Rust не так. Выражения вычисляют значение и составляют большую часть остального кода, который вы напишете на Rust. Рассмотрим математическую операцию, к примеру 5 + 6 , которая является выражением, вычисляющим значение 11 . Выражения могут быть частью операторов: в листинге 3-1 6 в операторе let y = 6; является выражением, которое вычисляется в значение 6 . Вызов функции - это выражение. Вызов макроса - это выражение. Новый блок области видимости, созданный с помощью фигурных скобок, представляет собой выражение, например: Имя файла: src/main.rs Это выражение: является блоком, который в данном случае вычисляется как 4 . Это значение затем привязывается к y как часть оператора let . Обратите внимание, что x + 1 не имеет точки с запятой в конце, в отличие от большинства строк, которые вы видели до сих пор. Выражения не включают точку с запятой в конце. Если добавить точку с запятой в конец выражения, то оно превратится в оператор, и не вернёт значение. Помните об этом, когда будете изучать возвращаемые функцией значения и выражения. Функции с возвращаемыми значениями Функции могут возвращать значения коду, который их вызывает. Мы не называем возвращаемые значения, но мы должны объявить их тип после стрелки ( -> ). В Rust возвращаемое значение функции является синонимом значения конечного выражения в блоке тела функции. Вы можете раньше выйти из функции и вернуть значение, используя ключевое слово return и указав значение, но большинство функций неявно возвращают последнее выражение. Вот пример такой функции: Имя файла: src/main.rs fn main () { let y = { let x = 3 ; x + 1 }; println! ( "The value of y is: {y}" ); } { let x = 3 ; x + 1 } В коде функции five нет вызовов функций, макросов или даже операторов let - есть только одно число 5 . Это является абсолютно корректной функцией в Rust. Заметьте, что возвращаемый тип у данной функции определён как -> i32 . Попробуйте запустить этот код. Вывод будет таким: Значение 5 в five является возвращаемым функцией значением, поэтому возвращаемый тип - i32 . Рассмотрим пример более детально. Здесь есть два важных момента: во-первых, строка let x = five(); показывает использование возвращаемого функцией значения для инициализации переменной. Так как функция five возвращает 5 , то эта строка эквивалентна следующей: Во-вторых, у функции five нет параметров и определён тип возвращаемого значения, но тело функции представляет собой одинокую 5 без точки с запятой, потому что это выражение, значение которого мы хотим вернуть. Рассмотрим другой пример: Имя файла: src/main.rs Запуск кода напечатает The value of x is: 6 . Но если поставить точку с запятой в конце строки, содержащей x + 1 , превратив её из выражения в оператор, мы получим fn five () -> i32 { 5 } fn main () { let x = five(); println! ( "The value of x is: {x}" ); } $ cargo run Compiling functions v0.1.0 (file:///projects/functions) Finished dev [unoptimized + debuginfo] target(s) in 0.30s Running `target/debug/functions` The value of x is: 5 let x = 5 ; fn main () { let x = plus_one( 5 ); println! ( "The value of x is: {x}" ); } fn plus_one (x: i32 ) -> i32 { x + 1 } ошибку. Имя файла: src/main.rs Компиляция данного кода вызывает следующую ошибку: Основное сообщение об ошибке "несовпадение типов" раскрывает ключевую проблему этого кода. Определение функции plus_one сообщает, что будет возвращено i32 , но операторы не вычисляют значение, что и выражается единичным типом () Следовательно, ничего не возвращается, что противоречит определению функции и приводит к ошибке. В этом выводе Rust выдаёт сообщение, которое, возможно, поможет исправить эту проблему: он предлагает удалить точку с запятой для устранения ошибки. fn main () { let x = plus_one( 5 ); println! ( "The value of x is: {x}" ); } fn plus_one (x: i32 ) -> i32 { x + 1 ; } $ cargo run Compiling functions v0.1.0 (file:///projects/functions) error[E0308]: mismatched types --> src/main.rs:7:24 | 7 | fn plus_one(x: i32) -> i32 { | -------- ^^^ expected `i32`, found `()` | | | implicitly returns `()` as its body has no tail or `return` expression 8 | x + 1; | - help: remove this semicolon For more information about this error, try `rustc --explain E0308`. error: could not compile `functions` due to previous error Комментарии Все хорошие программисты, создавая программный код, стремятся сделать его простым для понимания. Бывают всё же случаи, когда дополнительное описание просто необходимо. В этих случаях программисты пишут заметки (или как их ещё называют, комментарии). Комментарии игнорируются компилятором, но для тех кто код читает - это очень важная часть документации. Пример простого комментария: В Rust комментарии должны начинаться двумя символами // и простираются до конца строки. Чтобы комментарии поместились на более чем одной строке, необходимо разместить // на каждой строке, как в примере: Комментарии могут быть размещены в конце строки имеющей код: Файл: src/main.rs Но чаще вы увидите их использование в следующем формате, когда комментарий размещён на отдельной строке над кодом, который комментируется: Файл: src/main.rs Также в Rust есть другой тип комментариев - документирующие комментарии, которые мы обсудим в разделе "Публикация пакета на Crates.io" Главы 14. // Hello, world. // So we’re doing something complicated here, long enough that we need // multiple lines of comments to do it! Whew! Hopefully, this comment will // explain what’s going on. fn main () { let lucky_number = 7 ; // I’m feeling lucky today } fn main () { // I’m feeling lucky today let lucky_number = 7 ; } Управляющие конструкции Способность запускать некоторый код в зависимости от истинности условия или выполнять некоторый код многократно, пока условие истинно, является базовым элементом большинства языков программирования. Наиболее распространёнными конструкциями, позволяющими управлять потоком выполнения кода в Rust, являются выражения if и циклы. Выражения if Выражение if позволяет разветвлять код в зависимости от условий. Вы задаёте условие, а затем объявляете: "Если это условие соблюдено, то выполнить этот блок кода. Если условие не соблюдается, не выполнять этот блок кода". Для изучения выражения if создайте новый проект под названием branches в каталоге projects. В файл src/main.rs поместите следующий код: Имя файла: src/main.rs Все выражения if начинаются с ключевого слова if , за которым следует условие. В этом случае условие проверяет, имеет ли переменная number значение меньше 5. Если условие истинно, мы помещаем блок исполняемого кода сразу после условия внутри фигурных скобок. Блоки кода, связанные с условиями в выражениях if , иногда называются ответвлениями, точно так же, как ответвления в выражениях match , которые мы обсуждали в разделе «Сравнение догадки с секретным числом» Главы 2. Опционально можно включить выражение else , которое мы используем в данном примере, чтобы предоставить программе альтернативный блок выполнения кода, выполняющийся при ложном условии. Если не указать выражение else и условие будет ложным, программа просто пропустит блок if и перейдёт к следующему фрагменту кода. Попробуйте запустить этот код. Появится следующий результат: fn main () { let number = 3 ; if number < 5 { println! ( "condition was true" ); } else { println! ( "condition was false" ); } } Попробуйте изменить значение number на значение, которое делает условие false и посмотрите, что произойдёт: Запустите программу снова и посмотрите на вывод: Также стоит отметить, что условие в этом коде должно быть логического типа bool . Если условие не является bool , возникнет ошибка. Например, попробуйте запустить следующий код: Имя файла: src/main.rs На этот раз условие if вычисляется в значение 3 , и Rust бросает ошибку: Ошибка говорит, что Rust ожидал тип bool , но получил значение целочисленного типа. В отличии от других языков вроде Ruby и JavaScript, Rust не будет пытаться автоматически конвертировать нелогические типы в логические. Необходимо явно и всегда использовать if с логическим типом в качестве условия. Если нужно, чтобы блок $ cargo run Compiling branches v0.1.0 (file:///projects/branches) Finished dev [unoptimized + debuginfo] target(s) in 0.31s Running `target/debug/branches` condition was true let number = 7 ; $ cargo run Compiling branches v0.1.0 (file:///projects/branches) Finished dev [unoptimized + debuginfo] target(s) in 0.31s Running `target/debug/branches` condition was false fn main () { let number = 3 ; if number { println! ( "number was three" ); } } $ cargo run Compiling branches v0.1.0 (file:///projects/branches) error[E0308]: mismatched types --> src/main.rs:4:8 | 4 | if number { | ^^^^^^ expected `bool`, found integer For more information about this error, try `rustc --explain E0308`. error: could not compile `branches` due to previous error кода if запускался только, когда число не равно 0 , то, например, мы можем изменить выражение if на следующее: Имя файла: src/main.rs Будет напечатана следующая строка number was something other than zero Обработка нескольких условий с помощью else if Можно использовать несколько условий, комбинируя if и else в выражении else if Например: Имя файла: src/main.rs У этой программы есть четыре возможных пути выполнения. После её запуска вы должны увидеть следующий результат: Во время выполнения этой программы по очереди проверяется каждое выражение if и выполняется первое тело, для которого условие истинно. Заметьте, что хотя 6 делится на 2, мы не видим ни вывода number is divisible by 2 , ни текста number is not divisible by 4, 3, or 2 из блока else . Так происходит потому, что Rust выполняет блок только для первого истинного условия, а обнаружив его, даже не проверяет остальные. fn main () { let number = 3 ; if number != 0 { println! ( "number was something other than zero" ); } } fn main () { let number = 6 ; if number % 4 == 0 { println! ( "number is divisible by 4" ); } else if number % 3 == 0 { println! ( "number is divisible by 3" ); } else if number % 2 == 0 { println! ( "number is divisible by 2" ); } else { println! ( "number is not divisible by 4, 3, or 2" ); } } $ cargo run Compiling branches v0.1.0 (file:///projects/branches) Finished dev [unoptimized + debuginfo] target(s) in 0.31s Running `target/debug/branches` number is divisible by 3 Использование множества выражений else if приводит к загромождению кода, поэтому при наличии более чем одного выражения, возможно, стоит провести рефакторинг кода. В главе 6 описана мощная конструкция ветвления Rust для таких случаев, называемая match Использование if в let-операторах Поскольку if является выражением, его можно использовать в правой части оператора let для присвоения результата переменной, как в листинге 3-2. Имя файла: src/main.rs Листинг 3-2: Присвоение результата выражения if переменной Переменная number будет привязана к значению, которое является результатом выражения if . Запустим код и посмотрим, что происходит: Вспомните, что блоки кода вычисляются последним выражением в них, а числа сами по себе также являются выражениями. В данном случае, значение всего выражения if зависит от того, какой блок выполняется. При этом значения, которые могут быть результатами каждого из ветвей if , должны быть одного типа. В Листинге 3-2, результатами обеих ветвей if и else являются целочисленный тип i32 . Если типы не совпадают, как в следующем примере, мы получим ошибку: Имя файла: src/main.rs При попытке компиляции этого кода, мы получим ошибку. Ветви if и else представляют несовместимые типы значений, и Rust точно указывает, где искать fn main () { let condition = true ; let number = if condition { 5 } else { 6 }; println! ( "The value of number is: {number}" ); } $ cargo run Compiling branches v0.1.0 (file:///projects/branches) Finished dev [unoptimized + debuginfo] target(s) in 0.30s Running `target/debug/branches` The value of number is: 5 fn main () { let condition = true ; let number = if condition { 5 } else { "six" }; println! ( "The value of number is: {number}" ); } проблему в программе: Выражение в блоке if вычисляется как целочисленное, а выражение в блоке else вычисляется как строка. Это не сработает, потому что переменные должны иметь один тип, а Rust должен знать во время компиляции, какого типа переменная number . Зная тип number , компилятор может убедиться, что тип действителен везде, где мы используем number . Rust не смог бы этого сделать, если бы тип number определялся только во время выполнения. Компилятор усложнился бы и давал бы меньше гарантий в отношении кода, если бы ему приходилось отслеживать несколько гипотетических типов для любой переменной. Повторение выполнения кода с помощью циклов Часто бывает полезно выполнить блок кода более одного раза. Для этой задачи Rust предоставляет несколько циклов, которые позволяют выполнить код внутри тела цикла до конца, а затем сразу же вернуться в начало. Для экспериментов с циклами давайте создадим новый проект под названием loops. В Rust есть три вида циклов: loop , while и for . Давайте попробуем каждый из них. Повторение выполнения кода с помощью loop Ключевое слово loop говорит Rust выполнять блок кода снова и снова до бесконечности или пока не будет явно приказано остановиться. В качестве примера, измените код файла src/main.rs в каталоге проекта loops на код ниже: Имя файла: src/main.rs $ cargo run Compiling branches v0.1.0 (file:///projects/branches) error[E0308]: `if` and `else` have incompatible types --> src/main.rs:4:44 | 4 | let number = if condition { 5 } else { "six" }; | - ^^^^^ expected integer, found `&str` | | | expected because of this For more information about this error, try `rustc --explain E0308`. error: could not compile `branches` due to previous error fn main () { loop { println! ( "again!" ); } } Когда запустим эту программу, увидим, как again! печатается снова и снова, пока не остановить программу вручную. Большинство терминалов поддерживают комбинацию клавиш ctrl-c для прерывания программы, которая застряла в непрерывном цикле. Попробуйте: Символ ^C обозначает место, где было нажато ctrl-c . В зависимости от того, где находился код в цикле в момент получения сигнала прерывания, вы можете увидеть или не увидеть слово again! , напечатанное после ^C К счастью, Rust также предоставляет способ выйти из цикла с помощью кода. Ключевое слово break нужно поместить в цикл, чтобы указать программе, когда следует прекратить выполнение цикла. Напоминаем, мы делали так в игре "Угадайка" в разделе "Выход после правильной догадки" Главы 2, чтобы выйти из программы, когда пользователь выиграл игру, угадав правильное число. Мы также использовали continue в игре "Угадайка", которая указывает программе в цикле пропустить весь оставшийся код в данной итерации цикла и перейти к следующей итерации. Возвращение значений из циклов Одно из применений loop - это повторение операции, которая может закончиться неудачей, например, проверка успешности выполнения потоком своего задания. Также может понадобиться передать из цикла результат этой операции в остальную часть кода. Для этого можно добавить возвращаемое значение после выражения break , которое используется для остановки цикла. Это значение будет возвращено из цикла, и его можно будет использовать, как показано здесь: $ cargo run Compiling loops v0.1.0 (file:///projects/loops) Finished dev [unoptimized + debuginfo] target(s) in 0.29s Running `target/debug/loops` again! again! again! again! ^Cagain! Перед циклом мы объявляем переменную с именем counter и инициализируем её значением 0 . Затем мы объявляем переменную с именем result для хранения значения, возвращаемого из цикла. На каждой итерации цикла мы добавляем 1 к переменной counter , а затем проверяем, равен ли счётчик 10 . Когда это происходит, мы используем ключевое слово break со значением counter * 2 . После цикла мы ставим точку с запятой для завершения инструкции, присваивающей значение result Наконец, мы выводим значение в result , равное в данном случае 20. |