# 13차 러스트 스터디 ## 담당 - 발표: 유병조 님 - 리뷰: 허신 님 - 서기: 정연집 님 ## 과제 리뷰 ### [디자인패턴 만들어보기](https://github.com/JannLee/rust-study) ## 18. 값의 구조와 매칭되는 패턴 - 패턴은 단순하거나 복잡한 타입의 구조에 값들을 비교하기 위한 러스트의 특별한 문법 - 패턴의 조합 - 리터럴 값(Literals) - 분해한 배열(Array), 열거형(Enum), 구조체(Struct), 튜플(Tuple) - 변수(Variable) - 와일드카드(Wildcard) - 임시 값(Placeholders) ### 18.1. 패턴이 사용될 수 있는 모든 곳 #### match 갈래 - match표현은 match 키워드, 대응 시킬 값, 갈래들로 이루어지고, 각 갈래는 갈래를 나타내는 패턴, 해당 패턴에 값이 대응 될때 실행할 표현으로 구성됩니다. ``` rust match 값 { 패턴 => 표현, 패턴 => 표현, 패턴 => 표현, } ``` - match 표현을 사용하기 위해 지켜야할 사항 - match 표현에 대응 시킬 값이 가질 수 있는 모든 경우의 수를 빠짐 없이 표현해야 함. - '_' 키워드 - 명시 하지 않은 값들을 무시할 때 유용 #### if let 조건 표현 - 'if let', 'else if', 'else if let' 표현을 섞어서 사용 가능 - if let표현의 단점 - match표현과 다르게 컴파일러가 해당 구문이 모든 경우를 다뤘는지 판단하지 않는다는 점 - match와 차이점 - 'if let'의 경우, 모든 경우수를 매칭시키지 않아도 된다. ``` rust fn main() { let favorite_color: Option<&str> = None; let is_tuesday = false; let age: Result<u8, _> = "34".parse(); if let Some(color) = favorite_color { println!("Using your favorite color, {}, as the background", color); } else if is_tuesday { println!("Tuesday is green day!"); } else if let Ok(age) = age { if age > 30 { println!("Using purple as the background color"); } else { println!("Using orange as the background color"); } } else { println!("Using blue as the background color"); } } ``` #### while let 조건 루프 - 조건에 만족하면 계속 반복하게 하는 구문 ``` rust #![allow(unused)] fn main() { let mut stack = Vec::new(); stack.push(1); stack.push(2); stack.push(3); while let Some(top) = stack.pop() { println!("{}", top); } ``` #### for 루프 - 패턴 예시 - for x in y에서 x가 패턴 ``` rust #![allow(unused)] fn main() { let v = vec!['a', 'b', 'c']; for (index, value) in v.iter().enumerate() { println!("{} is at index {}", value, index); } // 튜플을 분해할 수 있음 ``` - enumerate 메소드 - 해당 반복자를 자신의 값과 값의 index를 포함한 튜플들을 순회 #### let 구문 - let PATTERN = EXPRESSION; - 변수명 자체가 하나의 패턴이 된다. ```rust let PATTERN = EXPRESSION; ``` - 튜플 매칭시 갯수가 서로 맞아야 한다. - _를 사용해 값을 무시할 수 있음 #### 함수의 매개변수 - 함수 매개변수는 패턴 ``` rust fn foo(x: i32) { // x가 패턴 // code goes here } ``` - 클로저도 동일하게 사용이 가능하다 ### 18.2. 반증 가능성: 패턴의 매칭이 실패할 수도 있는 경우 - 반증 가능 패턴 - 주어진 값에 대응이 실패할 수 있는 패턴. - ex) if let Some(x) = a_value; // a_value가 None일때 대응하지 못해 실패 - 반증 불가 패턴 - 주어진 어떠한 값에도 대응되는 패턴 (절대 실패할 수 없는 패턴) - ex) let x = 5; - 'let', 'for'구문 - 반증불가한 패턴만 허용 - 'if let', 'while let' - 반증 가능 패턴만 허용 - let 에서 반증 불가 패턴 사용시, ``` rust let Some(x) = some_option_value; // => if let을 사용 ``` ### 18.3. 패턴 문법의 모든 것 #### 18.3.1 리터럴 매칭 - 패턴과 리터럴을 직접 매칭하는 코드 ``` rust #![allow(unused)] fn main() { let x = 1; match x { 1 => println!("one"), 2 => println!("two"), 3 => println!("three"), _ => println!("anything"), } } ``` #### 18.3.2 명명 변수 매칭 - 갈래에서 y 변수를 새로 만들어 기존의 것이 가려지도록 한 match 표현 ```rust fn main() { let x = Some(5); let y = 10; match x { Some(50) => println!("Got 50"), // 정의된 변수 x 와 매칭되지 않으니, 해당 코드는 실행되지 않고 넘어갑니다. Some(y) => println!("Matched, y = {:?}", y), // 기존의 y가 아닌 새 변수 y이며 x의 값인 5가 대입됨 _ => println!("Default case, x = {:?}", x), } println!("at the end: x = {:?}, y = {:?}", x, y); // 여기서 y는 기존 변수 y이므로 y = 10 } // 출력 // Matched, y = 5 // at the end: x = Some(5), y = 10 ``` #### 18.3.3 다중 패턴 - 여러 패턴 패칭 - '|'키워드: or를 의미 ```rust= #![allow(unused)] fn main() { let x = 1; match x { 1 | 2 => println!("one or two"), 3 => println!("three"), _ => println!("anything"), } } ``` #### 18.3.4 `...`를 이용한 값의 범위 매칭 - `1 | 2 | 3 | 4 | 5` 대신 `1 ... 5`로 간편하게 표현 가능 - 숫자 값이나 char 값에만 사용할 수 있습니다. ```rust let x = 5; match x { 1 ... 5 => println!("one through five"), _ => println!("something else"), } ``` - char 값 매칭 예제 ``` rust #![allow(unused)] fn main() { let x = 'c'; match x { 'a' ... 'j' => println!("early ASCII letter"), 'k' ... 'z' => println!("late ASCII letter"), _ => println!("something else"), } } ``` #### 18.3.5 값을 해체하여 분리하기 - 패턴을 이용해 구조체, 열거형, 튜플, 참조자 등의 값들을 해체(destructuring) ##### 구조체 해체하기 ```rust struct Point { x: i32, y: i32, } fn main() { let p = Point { x: 0, y: 7 }; //p 변수의 필드인 x 와 y 에 각각 대응되는 a 와 b 변수를 생성 let Point { x: a, y: b } = p; assert_eq!(0, a); assert_eq!(7, b); } ``` - 패턴내 변수의 이름이 꼭 구조체 내 필드명과 일치할 필요 없음. 하지만 기억하기 쉽도록 필드명을 일치시키는 것이 일반적이다. ##### 열거형 해체 - 열거형을 해체하기위한 패턴은 해당 열거형에 내장된 데이터의 정의 방식이 일치해야한다. - 다른 종류의 값들을 갖는 열거형 variant 해체 ```rust enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(i32, i32, i32), } fn main() { let msg = Message::ChangeColor(0, 160, 255); match msg { Message::Quit => { println!("The Quit variant has no data to destructure.") }, Message::Move { x, y } => { println!( "Move in the x direction {} and in the y direction {}", x, y ); } Message::Write(text) => println!("Text message: {}", text), Message::ChangeColor(r, g, b) => { println!( "Change the color to red {}, green {}, and blue {}", r, g, b ) } } } // 출력 // Change the color to red 0, green 160, and blue 255 ``` ##### 참조자 해체 - 패턴내에서 '&'키워드를 이용해 참조자를 해체해야함. ```rust #![allow(unused)] fn main() { struct Point { x: i32, y: i32, } let points = vec![ Point { x: 0, y: 0 }, Point { x: 1, y: 5 }, Point { x: 10, y: -3 }, ]; let sum_of_squares: i32 = points .iter() .map(|&Point { x, y }| x * x + y * y) .sum(); } ``` - &Point { x, y } 에서 & 를 뺀다면 타입 불일치(type mismatch) 오류가 발생 ```result error[E0308]: mismatched types --> | 14 | .map(|Point { x, y }| x * x + y * y) | ^^^^^^^^^^^^ expected &Point, found struct `Point` | = note: expected type `&Point` found type `Point` ``` - ##### 구조체와 튜플 해체 ``` rust let ((feet, inches), Point {x, y}) = ((3, 10), Point { x: 3, y: -10 }); ``` #### 18.3.6 패턴 내에서 값 무시하기 - 패턴 내 값을 _, ..을 사용해 무시할 수 있다. ##### _ 를 이용해 전체 값 무시하기 - '_'언더스코어 와일드카드 패턴 - 어떤 값과도 매치되지만 값으로 바인드(bind) 하지는 않는 것으로 사용 - 함수 시그니처에서 _ 사용하기 - 사용하지 않는 매개 변수에 _를 사용하면 컴파일러가 경고를 하지 않음 - 언더 스코어가 아닌 이름을 사용할 경우 경고. ```rust fn foo(_: i32, y: i32) println!("This code only uses the y parameter: {}", y); } fn main() { foo(3, 4); // 첫번째 인자로 전달된 값인 3 을 완벽히 무시 } // 출력 // This code only uses the y parameter: 4 ``` ##### 중첩된 _ 를 이용해 값의 일부 무시하기 - _ 를 다른 패턴 내에 사용해서 값의 일부를 무시할 수도 있습니다 - 값의 일부를 테스트 하려는데 해당 코드에서 그 외의 나머지 부분은 필요하지 않을때 이 기능을 사용할 수 있습니다. ```rust #![allow(unused)] fn main() { let mut setting_value = Some(5); let new_setting_value = Some(10); match (setting_value, new_setting_value) { (Some(_), Some(_)) => { println!("Can't overwrite an existing customized value"); } _ => { setting_value = new_setting_value; } } println!("setting is {:?}", setting_value); } // 실행 결과 // Can't overwrite an existing customized value 와 setting is Some(5) 를 출력 ``` ##### 언더스코어로 시작하는 이름을 이용해 쓰이지 않는 변수 무시하기 - 프로토타입을 만드는 중이거나 프로젝트를 막 시작했을 때와 같이 아직 사용하진 않아도 미리 변수를 만들어 두는 것이 유용할 때도 있습니다. - _는 값을 바인드하지 않고 - _변수명은 값을 바인드한다. ```rust fn main() { let _x = 5; let y = 10; } ``` ##### .. 를 이용해 값의 나머지 부분 무시하기 - 값의 일부만 사용하고 나머지는 무시하기 위해 '..' 구문을 사용 - .. 패턴은 우리가 패턴에서 명시하지 않은 값의 나머지 부분을 모두 무시 ```rust #![allow(unused)] fn main() { struct Point { x: i32, y: i32, z: i32, } let origin = Point { x: 0, y: 0, z: 0 }; match origin { Point { x, .. } => println!("x is {}", x), } } ``` - ..을 사용할 땐 모호하지 않아야 한다. ``` rust fn main() {//모호한 예시 let numbers = (2, 4, 8, 16, 32); match numbers { (.., second, ..) => { println!("Some numbers: {}", second) }, } } ``` #### 18.3.7 ref 와 ref mut 를 이용해 패턴 내에서 참조자 생성하기 - 가변 참조자 사용 - 매치된 패턴 내에서 값을 변경하기 위해 가변 참조자를 생성하려면 &mut 대신 ref mut 을 사용해야 합니다 - & 는 새 참조자를 생성하는 것이 아닌 이미 존재하는 가변 참조자를 매치시키는데 사용 #### 18.3.8 매치 가드를 이용한 추가 조건 - 매치 가드(match guard) - match 갈래 뒤에 추가로 붙는 if 조건으로, 이것이 있을 경우 패턴 매칭과 해당 조건이 모두 만족되어야 해당 갈래가 선택됨. ```rust #![allow(unused)] fn main() { let num = Some(4); match num { Some(x) if x < 5 => println!("less than five: {}", x), Some(x) => println!("{}", x), None => (), } } ``` - match 표현의 패턴 내에서 새로 생성한 변수가 기존에 있던 match 바깥의 변수를 가려버려서 기존의 변수를 테스트 할 수 없다. 이문제를 해결하기 위한 예시는 아래와 같다. ```rust fn main() { let x = Some(5); let y = 10; match x { Some(50) => println!("Got 50"), Some(n) if n == y => println!("Matched, n = {:?}", n), _ => println!("Default case, x = {:?}", x), } println!("at the end: x = {:?}, y = {:?}", x, y); } ``` ```rust #![allow(unused)] fn main() { let x = 4; let y = false; match x { 4 | 5 | 6 if y => println!("yes"), _ => println!("no"), } } ``` - 첫 번째 갈래의 패턴에 매치 - 두 번째 갈래로 이동하고 매치되며, 프로그램은 no 를 출력 #### 18.3.9 @ 바인딩 - at 연산자인 @ - 해당 값이 패턴과 매치되는지 확인하는 동시에 해당 값을 갖는 변수를 생성할 수 있도록 해줍니다. ```rust #![allow(unused)] fn main() { enum Message { Hello { id: i32 }, } let msg = Message::Hello { id: 5 }; match msg { Message::Hello { id: id_variable @ 3...7 } => { println!("Found an id in range: {}", id_variable) }, Message::Hello { id: 10...12 } => { println!("Found an id in another range") }, Message::Hello { id } => { println!("Found some other id: {}", id) }, } } ``` - 3...7 범위 앞에 id_variable @ 를 지정하는 것으로 값이 범위 패턴과 매치되는지 테스트하면서 해당 값을 캡쳐할(capturing) 수 있습니다. #### 정리 - 러스트의 패턴은 다른 종류의 데이터를 구별하는데 굉장히 유용 - match 표현 내에서 패턴을 사용하면 러스트가 여러분의 패턴이 모든 가능한 값을 커버할 수 있다는 것을 보장 ## 차주 일정 - 스터디 장: 19. 고급 기능들 - 날짜: 2022-09-07 - 과제: 18장에서 배운 것들을 활용하여 자신이 지금까지 수행한 과제 중 하나 리팩토링하기 ## 벌금현황 - 이지한: 0 - 김기덕: 0 - 유병조: 0 - 이명수: 0 - 이재현: 0 - 정연집: 10,000 - 허신: 10,000 ## 회고 - 이지한: - 김기덕: [github 다양한 디자인패턴 저장소 목록](https://github.com/search?o=desc&q=rust+design+pattern&s=stars&type=Repositories)을 참고해서 과제했어요! 이번장에서 공부한 내용을 좀 능숙하게 쓸 수 있으면 좋겠습니다! - 유병조: 패턴을 사용해서 간결하게 코드를 짤 수 있을거 같아서 좋아요~ - 이명수: 싱글톤 구현하다 낙오했어요 ㅜㅜ - 이재현: 지금까지 warning때 자주 봤던 _나 for문 같은데서 썼던 ..를 패턴이라는 개념으로 정리가 되서 좋았던 것 같습니다 - 정연집: 많이 쓸 것 같은 매칭에 대해 자세히 배운 것 같아서 좋았습니다. - 허신: 이전에 if let 구문이 이해가 안됐었는데 오늘 17장 "패턴" 문법 통해서 잘 정리된 것 같아요.