В Rust есть управление потоком программы через конструкции IF, ELSE IF, ELSE:
lettest_number=6;iftest_number%4==0{println!("Divisible by 4");}elseiftest_number%3==0{// Проверка останавливается на первом
println!("Divisible by 3");// выполнимом условии, дальнейшие проверки
}elseiftest_number%2==0{// пропускаются.
println!("Divisible by 2");}else{println!("Number is not divisible by 4, 3 or 2");}
Конструкция IF является выражением (expression) и возвращает значение:
letcondition=true;letnumber=ifcondition{"aa"}else{"ab"};// присваивание результата IF
println!("Number is {number}");
Используйте if как выражение для компактности. Для сложных случаев лучше переходить к match.
IF LET
В отличие от match, в котором нужно обязательно перебрать все переданные варианты, if let позволяет обработать лишь один вариант, отбросив все остальные (не реагируя на них). В то время как match можно сравнить с вендинговым автоматом, if let - это фильтр.
Применение - проверить, что в Option есть значение:
letusername: Option<String>=Some("cool_teen".to_string());// длинный путь с match:
matchusername{Some(name)=>println!("Hello {}!",name),None=>{},// ненужный пустой блок 🤮
}// короткий путь:
ifletSome(name)=username{println!("Hello {}!",name);// работает только если Some существует
}// Не надо обрабатыватьNone!
Применение - проверить, что Result успешен:
letfile_result: Result<File,std::io::Error>=File::open("config.txt");// нас волнует только, если сработало:
ifletOk(file)=file_result{println!("File opened successfully!");// далее использовать 'file'
}
if let работает с любым паттерном:
// проверить точное значение:
iflet42=answer{println!("The meaning of life!");}// сравнить 1ый элемент кортежа
letcoordinates=(10,20);iflet(x,20)=coordinates{println!("Y is 20, X is {}",x);}// проверить несколько условий с помощью `|` (or)
enumStatus{Active,Pending,Inactive,}letstate=Status::Pending;ifletStatus::Active|Status::Pending=state{println!("User can log in");}
❗Эффективно применять if let тогда, когда условие “все остальные случаи” _ => {} в конструкции match пустое.
IF LET ELSE
Можно комбинировать if let и else для обработки “противоположного” результата:
ifletSome(score)=high_score{println!("New high score: {}!",score);}else{println!("No high score yet. Play a game!");}
LOOPS
Три варианта организовать цикл: через операторы loop, while, for.
Loop
loop - организация вечных циклов. Конструкция loop является выражением (expression), поэтому возвращает значение.
letmutcounter=0;letresult=loop{counter+=1;ifcounter==10{breakcounter*2;// выход из вечного цикла
}};// ";" нужно, т.к. было выражение
println!("The result is {result}");
Если делать вложенные циклы, то можно помечать их меткой, чтобы выходить с break на нужный уровень.
for - цикл по множествам элементов. В том числе можно задать подмножество чисел.
foriin(1..10).rev(){// .rev() - выдача подмножества в обратную сторону
println!("Value: {i}");}println!("ЗАПУСК!");
Функции
Вызов функции: указатель на начало функции кладётся на стек (под каждую функцию выделяется место на стеке - stack frame). Стек имеет ограничения по размеру. Можно это проверить, написав пример:
fnmain(){a();}fna(){println!("Calling B!");b();}fnb(){println!("Calling C!");c();}fnc(){println!("Calling A!");a();// бесконечный цикл вызовов
}
При запуске программы она бесконечно вызывает функции, пока не исчерпает место в stack frame функции main, и тогда программа падает с ошибкой:
thread 'main' has overflowed its stack
fatal runtime error: stack overflow
Abort trap: 6
Передача функций как аргументов
Тип функции — её сигнатура.
// Функция apply принимает 2 параметра:
// 1. x типа i32 (32-битное целое число)
// 2. f - функцию с сигнатурой fn(i32) -> i32,
// т.е. которая принимает i32 и возвращает i32
// Сама apply возвращает i32
fnapply(x: i32,f: fn(i32)-> i32)-> i32{// Применяем переданную функцию f к аргументу x
// и возвращаем результат
f(x)}// Функция принимает x типа i32 и возвращает x * 2 тоже типа i32
fndouble(x: i32)-> i32{x*2// удваивает входное число
}fnmain(){// Вызываем apply с двумя аргументами:
// 1. Число 5
// 2. Функция double
// double будет применена к 5, то есть 5 * 2 = 10
letresult=apply(5,double);println!("Удвоенное: {}",result);// Вывод: Удвоенное: 10
}
Exit
В Rust для “нормального” завершения программы используется функция std::process::exit из стандартной библиотеки. Она завершает программу немедленно с заданным кодом возврата, не вызывая панику.
usestd::process;fnmain(){println!("До выхода");process::exit(0);// Завершаем программу с кодом 0 (успех)
println!("Это не выведется");}
process::exit(code: i32) принимает код возврата (i32), который передаётся операционной системе.
Код 0 обычно означает “успешное завершение”, а ненулевые значения (напримр, 1) — “ошибка”.
После вызова exit программа завершается мгновенно — никакой код ниже не выполняется, даже если есть незавершённые операции.
Return
Можно использовать return в main. Это не то же самое, что exit (не мгновенно прерывает), но подходит для естественного завершения:
fnmain(){println!("Работаем...");return;// Завершаем с кодом 0
println!("Это не выведется");}
Result
Можно возвращать Result из main, чтобы указать успех или ошибку:
fnmain()-> Result<(),i32>{println!("Работаем...");Ok(());// Успех, код 0
// или Err(1) для ошибки с кодом 1
}
Если нужно мгновенное завершение с кодом возврата — используй std::process::exit;
Если надо завершить программу “правильно” в конце main — просто return или Result;
panic! - для случаев, когда программа должна “упасть” из-за ошибки.