Кортеж (tuple) представляет способ группировки данных разных типов в один составной тип. Кортежи имеют фиксированный тип в соответствии с вложенными данными. После определения кортежа его размер нельзя ни уменьшить, ни увеличить.
Формальный синтаксис определения кортежа:
let tuple = (значение1, значение2, ... значениеN);
Кортеж определяется в круглых скобках, внутри которых через запятую перечисляются составляющие кортеж значения.
Определение простейшего кортежа:
let user = ("Tom", 36, 1.78);
Здесь переменная user представляет кортеж, который состоит из трех элементов.
В примере выше тип элементов кортежа выводится автоматически, но также мы можем явно задать их типы:
let user: (&str, u8, f32) = ("Tom", 36, 1.78);
То есть в данном случае кортеж имеет тип (&str, u8, f32), а это значит, что первый его элемент должен представлять строку,
второй элемент - число типа u8, а третий - число с плавающей точкой типа f32.
После объявления кортежа мы можем обращаться к его элементам в виде:
название_кортежа.индек_элемента
После названия кортежа через точку указывается индекс элемента кортежа, при этом индексация начинается с нуля. Например:
fn main(){
let user: (&str, u8, f32) = ("Tom", 36, 1.78);
println!("Имя: {}", user.0);
println!("Возраст: {}", user.1);
println!("Рост: {}", user.2);
}
Консольный вывод:
Имя: Tom Возраст: 36 Рост: 1.78
Также мы можем изменять элементы кортежа, если его переменная определена с ключевым словом mut:
fn main(){
let mut user: (&str, u8, f32) = ("Tom", 36, 1.78);
user.0 = "Bob";
println!("Имя: {}", user.0); // Bob
}
Rust предоставляет такую возможность, как декомпозиция (destructuring) кортежа. Например, мы могли бы следующим образом присвоить значения кортежа переменным:
fn main(){
let user = ("Tom", 36, 1.78);
// присваиваем переменным значения кортежа
let name = user.0;
let age = user.1;
let height = user.2;
println!("Имя: {}", name);
println!("Возраст: {}", age);
println!("Рост: {}", height);
}
Но декомпозиция позволяет сократить код:
fn main(){
let user = ("Tom", 36, 1.78);
// декомпозиция кортежа на переменные
let (name, age, height) = user;
println!("Имя: {}", name);
println!("Возраст: {}", age);
println!("Рост: {}", height);
}
Но, возможно, нам нужны не все данные из кортежа. Например, нам не нужно получать рост человека. Тем не менее мы не можем написать, например, так:
let (name, age) = user; // !Ошибка
Нам обязательно надо указать столько же переменных, сколько значений в кортеже. Однако если какое-то значение нам не нужно, мы можем вместо соответствующей переменной указать прочерк:
let (name, age, _) = user;
Для сравнения кортежей применяется операция ==. Кортежи считаются равными, если равны их соответствующие значения. Пример сравнения кортежа:
fn main() {
let user = ("Tom", 36);
if ("Tom", 36) == user {
println!("name: Tom, age: 36");
}
}
К кортежу также, как и к другим типам применяется концепция владения. В Rust, когда вы присваиваете одну переменную другой, и это присвоение включает в себя типы, реализующие трейт Copy (например, целые числа и кортежи типов Copy), значение копируется, и вы все равно можете использовать исходную переменную. Однако при присвоении значения типа, который не реализует трейт Copy (например, кортеж, которые содержат значения типов, не реализующих данный трейт), то право собственности передается, и вы не можете впоследствии использовать исходную переменную. Например:
fn main(){
let user = (String::from("Tom"), 39);
let employee = user; // передача владения переменной employee
println!("User Name: {}", user.0); // ! Ошибка
println!("Employee Name: {}", employee.0);
}
Применяя ссылки, мы можем уйти от данной ошибки:
let user = (String::from("Tom"), 39);
let employee = &user; // нет передачи владения
Как и любой другой тип, кортеж может выступать параметром функции. Например:
fn display(user:(&str, i32)){
println!("name: {} age:{}", user.0, user.1);
}
fn main(){
let tom = ("Tom", 36);
display(tom);
}
Здесь функции display() принимает кортеж, причем первый элемент этого кортежа представляет тип &str или строку, а второй элемент - тип i32.
Причем мы можем сразу же разложить кортеж на уровне параметра:
// декомпозиция кортежа
fn display((name, age):(&str, i32)){
println!("name: {} age:{}", name, age);
}
fn main(){
let tom = ("Tom", 36);
display(tom);
}
Но стоит учитывать, что если кортеж содержит данные типов, которые не реализуют трейт Copy, то при передаче кортежа происходит передача владения. Например:
fn display(user:(String, i32)){
println!("name: {} age:{}", user.0, user.1);
}
fn main(){
let tom = (String::from("Tom"), 36);
display(tom);
display(tom); // ! Ошибка
}
Здесь при первом вызове функции display происходит передача владения кортежа. В итоге мы получаем ошибку. И в этом случае мы можем передавать кортеж по ссылке:
// получаем ссылку на кортеж
fn display(user: &(String, i32)){
println!("name: {} age:{}", user.0, user.1);
}
fn main(){
let tom = (String::from("Tom"), 36);
display(&tom);
display(&tom);
}
И также функция может возвращать кортеж:
fn get_default_point() -> (i32, i32){
(4, 25)
}
fn main(){
let (x, y) = get_default_point();
println!("x: {} y:{}", x, y);
}
Здесь функция get_default_point() возврашает кортеж, первый и второй элементы которого представляют тип i32.