模式解構賦值
模式匹配時可用於解構賦值,可解構的類型包括struct、enum、tuple、slice等等。
解構賦值時,可使用_
作為某個變量的佔位符,使用..
作為剩餘所有變量的佔位符(使用..
時不能產生歧義,例如(..,x,..)
是有歧義的)。當解構的類型包含了命名字段時,可使用fieldname
簡化fieldname: fieldname
的書寫。
解構struct
解構Struct時,會將待解構的struct各個字段和Pattern中的各個字段進行匹配,併為找到的字段變量進行賦值。
當Pattern中的字段名和字段變量同名時,可簡寫。例如P{name: name, age: age}
和P{name, age}
是等價的Pattern。
struct Point2 { x: i32, y: i32, } struct Point3 { x: i32, y: i32, z: i32, } fn main(){ let p = Point2{x: 0, y: 7}; // 等價於 let Point2{x: x, y: y} = p; let Point2{x, y} = p; println!("x: {}, y: {}", x, y); // 解構時可修改字段變量名: let Point2{x: a, y: b} = p; // 此時,變量a和b將被賦值 let ori = Point{x: 0, y: 0, z: 0}; match ori{ // 使用..忽略解構後剩餘的字段 Point3 {x, ..} => println!("{}", x), } }
解構enum
例如:
enum IPAddr { IPAddr4(u8,u8,u8,u8), IPAddr6(String), } fn main(){ let ipv4 = IPAddr::IPAddr4(127,0,0,1); match ipv4 { // 丟棄解構後的第四個值 IPAddr(a,b,c,_) => println!("{},{},{}", a,b,c), IPAddr(s) => println!("{}", s), } }
解構元組
#![allow(unused)] fn main() { let ((feet, inches), Point {x, y}) = ((3, 1), Point { x: 3, y: -1 }); }
@綁定變量名
當解構後進行模式匹配時,如果某個值沒有對應的變量名,則可以使用@
手動綁定一個變量名。
例如:
#![allow(unused)] fn main() { struct S(i32, i32); match S(1, 2) { // 如果匹配1成功,將其賦值給變量z // 如果匹配2成功,也將其賦值給變量z S(z @ 1, _) | S(_, z @ 2) => assert_eq!(z, 1), _ => panic!(), } }
再例如,匹配並解構一個數組:
#![allow(unused)] fn main() { let arr = ["x", "y", "z"]; match arr { [.., "!"] => println!("!!!"), // 匹配成功,start = ["x", "y"] [start @ .., "z"] => println!("starts: {:?}", start), ["a", end @ ..] => println!("ends: {:?}", end), rest => println!("{:?}", rest), } }
ref和mut修飾模式中的變量
當進行解構賦值時,很可能會將變量擁有的所有權轉移出去,從而使得原始變量變得不完整或直接失效。
struct Person{ name: String, age: i32, } fn main(){ let p = Person{name: String::from("junmajinlong"), age: 23}; let Person{name, age} = p; println!("{}", name); println!("{}", age); println!("{}", p.name); // 錯誤,name字段所有權已轉移 }
如果想要在解構賦值時不丟失所有權,有以下幾種方式:
#![allow(unused)] fn main() { // 方式一:解構表達式的引用 let Person{name, age} = &p; // 方式二:解構表達式的克隆,適用於可調用clone()方法的類型 // 但Person struct沒有clone()方法 // 方式三:在模式的某些字段或元素上使用ref關鍵字修飾變量 let Person{ref name, age} = p; let Person{name: ref n, age} = p; }
在模式中使用ref
修飾變量名相當於對被解構的字段或元素上使用&
進行引用。
#![allow(unused)] fn main() { let x = 5_i32; // x的類型:i32 let x = &5_i32; // x的類型:&i32 let ref x = 5_i32; // x的類型:&i32 let ref x = &5_i32; // x的類型:&&i32 }
因此,使用ref修飾了模式中的變量後,解構賦值時對應值的所有權就不會發生轉移,而是以只讀的方式借用給該變量。
如果想要對解構賦值的變量具有數據的修改權,需要使用mut
關鍵字修飾模式中的變量,但這樣會轉移原值的所有權,此時可不要求原變量是可變的。
#[derive(Debug)] struct Person { name: String, age: i32, } fn main() { let p = Person { name: String::from("junma"), age: 23, }; match p { Person { mut name, age } => { name.push_str("jinlong"); println!("name: {}, age: {}", name, age) }, } //println!("{:?}", p); // 錯誤 }
如果不想在可修改數據時丟失所有權,可在mut的基礎上加上ref關鍵字,就像&mut xxx
一樣。
#[derive(Debug)] struct Person { name: String, age: i32, } fn main() { let mut p = Person { // 這裡要改為mut p name: String::from("junma"), age: 23, }; match p { // 這裡要改為ref mut name Person { ref mut name, age } => { name.push_str("jinlong"); println!("name: {}, age: {}", name, age) }, } println!("{:?}", p); }
注意,使用ref
修飾變量只是借用了被解構表達式的一部分值,而不是借用整個值。如果要匹配的是一個引用,則使用&
。
#![allow(unused)] fn main() { let a = &(1,2,3); // a是一個引用 let (t1,t2,t3) = a; // t1,t2,t3都是引用類型&i32 let &(x,y,z) = a; // x,y,z都是i32類型 let &(ref xx,yy,zz) = a; // xx是&i32類型,yy,zz是i32類型 }
最後,也可以將match value{}
的value進行修飾,例如match &mut value {}
,這樣就不需要在模式中去加ref和mut了。這對於有多個分支需要解構賦值,且每個模式中都需要ref/mut修飾變量的match非常有用。
fn main() { let mut s = "hello".to_string(); match &mut s { // 對可變引用進行匹配 // 匹配成功時,變量也是對原數據的可變引用 x => x.push_str("world"), } println!("{}", s); }
匹配守衛(match guard)
匹配守衛允許匹配分支添加額外的後置條件:當匹配了某分支的模式後,再檢查該分支的守衛後置條件,如果守衛條件也通過,則成功匹配該分支。
#![allow(unused)] fn main() { let x = 33; match x { // 先範圍匹配,範圍匹配成功後,再檢查是否是偶數 // 如果範圍匹配沒有成功,則不會檢查後置條件 0..=50 if x % 2 == 0 => { println!("x in [0, 50], and it is an even"); }, 0..=50 => println!("x in [0, 50], but it is not an even"), _ => (), } }
注意,後置條件的優先級很低。例如:
#![allow(unused)] fn main() { // 下面兩個分支的寫法等價 4 | 5 | 6 if bool_expr => println!("yes"), (4 | 5 | 6) if bool_expr => println!("yes"), }
注意(1):對引用進行解構賦值時
在解構賦值時,如果解構的是一個引用,則被匹配的變量也將被賦值為對應元素的引用。
#![allow(unused)] fn main() { let t = &(1,2,3); // t是一個引用 let (t0,t1,t2) = t; // t0,t1,t2的類型都是&i32 let t0 = t.0; // t0的類型是i32而不是&i32,因為t.0等價於(*t).0 let t0 = &t.0; // t0的類型是&i32而不是i32,&t.0等價於&(t.0)而非(&t).0 }
因此,當使用模式匹配語法for i in t
進行迭代時:
- 如果t不是一個引用,則t的每一個元素都會move給i
- 如果t是一個引用,則i將是每一個元素的引用
- 同理,
for i in &mut t
和for i in mut t
也一樣
注意(2):對解引用進行匹配時
當match VALUE
的VALUE是一個解引用*xyz
時(因此,xyz是一個引用),可能會發生所有權的轉移,此時可使用xyz
或&*xyz
來代替*xyz
。具體原因請參考:解引用(deref)的所有權轉移問題。
下面是一個示例:
fn main() { // p是一個Person實例的引用 let p = &Person { name: "junmajinlong".to_string(), age: 23, }; // 使用&*p或p進行匹配,而不是*p // 使用*p將報錯,因為會轉移所有權 match &*p { Person {name, age} =>{ println!("{}, {}",name, age); }, _ => (), } } struct Person { name: String, age: u8, }