Оператор ? после значения Result указывает, что если функция возвращает объект Result, который представляет константу Ok,
то возвращается значение из константы Ok. Однако если значение Result предоставляет константу Err,
то Err будет возвращаться из всей вызывающей функции, в которой получен объект Result.
Рассмотрим следующий пример:
fn main() ->Result<(), String>{
let tom = create_person("Tom", 136)?;
println!("Name: {} Age: {}", tom.name, tom.age);
Ok(())
}
struct Person{ name: String, age:u8}
fn create_person(username: &str, userage: u8) -> Result<Person, String>{
if userage < 110{
let new_person = Person{name: String::from(username), age: userage };
Result::Ok(new_person)
}
else {
Result::Err(String::from("Некорректный возраст. Возраст должен быть меньше 110"))
}
}
Функция create_person() возвращает объект Result. Поэтому при ее вызове в main к ней можно применить оператор ?:
let tom = create_person("Tom", 136)?;
То есть здесь нам не надо применять никаких конструкций match или дополнительных методов типа unwrap() или expect(),
чтобы получить требуемый объект Person.
Однако в этом случае функция, которая вызывает функцию create_person() также должна возвращать объект Result. То есть в данном случае функция
main() должна определять в качестве возвращаемого типа тип Result. Но функция main является особой функцией,
которая может возращать только тип (). Этот тип еще называется unit type и аналогичен
типу void в других языках программирования.
Чтобы выйти из ситуации первый параметр типа в Result<T, E> (параметр T)
представляет тип (), а второй - тип ошибки (то есть тип String):
fn main() ->Result<(), String>{
Кроме того, в конце функция возвращает константу Ok(), в которую передается ():
Ok(())
При этом возвращать отдельно константу Err() не надо, поскольку это делает автоматически оператор ?
Стоит отметить, что в реальности оператор ? можно применять к функциям, которые возвращают объекты типов Result или
Option или других типов, которые реализуют трейт std::ops::Try.
Аналогично можно применять оператор ? для передачи информации об оишбке вверх по стеку вызовов:
fn main() -> Result<(), String>{
display_person("Tom", 36)?;
display_person("Tom", 136)?;
Ok(())
}
struct Person{ name: String, age:u8}
fn create_person(username: &str, userage: u8) -> Result<Person, String>{
if userage < 110{
let new_person = Person{name: String::from(username), age: userage };
Result::Ok(new_person)
}
else {
Result::Err(String::from("Некорректный возраст. Возраст должен быть меньше 110"))
}
}
fn display_person(username: &str, userage: u8) -> Result<(), String>{
let tom = create_person(username, userage)?;
println!("Name: {} Age: {}", tom.name, tom.age);
Ok(())
}
Здесь функция main вызывает функцию display_person, которая, в свою очередь вызывает функцию create_person. К обоим функциям применяется оператор ?. Таким образом, если возникнет ошибка в create_person, то эта же ошибка возникнет и в display_person и поднимется дальше до функции main.
Прекрасной особенностью оператора ? является то, что он позволяет организовать цепочки вызовов методов. Например:
fn main() ->Result<(), String> {
create_person("Tom", 40)?.print();
create_person("Bob", 44)?.print();
create_person("Sam", 132)?.print();
create_person("Alice", 36)?.print();
Ok(())
}
struct Person{ name: String, age:u8}
impl Person{
fn print(&self){
println!("Name: {} Age: {}", self.name, self.age);
}
}
fn create_person(username: &str, userage: u8) -> Result<Person, String>{
if userage < 110{
let new_person = Person{name: String::from(username), age: userage };
Result::Ok(new_person)
}
else {
Result::Err(String::from("Некорректный возраст. Возраст должен быть меньше 110"))
}
}
Здесь опять же у нас структура Person, для создания которой определена функция create_person. И для структуры Person также определен метод print. А в функции main мы пытаемся создать 4 таких структуры и вызвать у них метод print. Так, возьмем первый вызов:
create_person("Tom", 40)?.print();
В данном случае сначала создаем структуру Person с помощью функции create_person. Оператор ? после вызова функции указывает, что если возникнет ошибка, то возвращаем ее из функции (функции main). Если же ошибки нет, то у созданного объекта Person далее вызываем метод print. И здесь очевидно, что при создании третьей структуры мы получим ошибку:
create_person("Sam", 132)?.print();
Соответственно функция main после этого завершит работу, и мы получим следующий консольный вывод:
Name: Tom Age: 40 Name: Bob Age: 44 Error: "Некорректный возраст. Возраст должен быть меньше 110"
Причем таким образом также можно вызывать методы, которые возвращают другие ошибки.