# Rust의 가장 미묘한 구문

> Clean Markdown view of GeekNews topic #17593. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=17593](https://news.hada.io/topic?id=17593)
- GeekNews Markdown: [https://news.hada.io/topic/17593.md](https://news.hada.io/topic/17593.md)
- Type: news
- Author: [xguru](https://news.hada.io/@xguru)
- Published: 2024-11-05T10:25:02+09:00
- Updated: 2024-11-05T10:25:02+09:00
- Original source: [zkrising.com](https://zkrising.com/writing/rusts-most-subtle-syntax/)
- Points: 14
- Comments: 3

## Summary

Rust의 `let`과 `const`는 각각 변수 선언과 컴파일 타임 상수를 정의하는 데 사용되며, `const`는 선언 순서에 상관없이 사용할 수 있어 자바스크립트 출신 프로그래머에게는 혼란을 줄 수 있습니다. Rust의 패턴 매칭은 `match` 구문을 통해 다양한 경우를 처리할 수 있으며, 상수는 패턴으로 사용되어 매칭의 유연성을 높입니다. 그러나 상수와 변수의 이름이 혼동될 수 있어, 특히 대소문자 규칙을 지키지 않으면 코드의 의도와 다르게 동작할 수 있는 위험이 있습니다. 사실 큰 문제가 아니긴 한데 Rust 의 let 과 const 구문에 대해 깊이 이해하기에는 좋은 글인 거 같아요.

## Topic Body

### Rust의 `let`과 `const`  
- `let`은 새로운 변수를 선언하는 데 사용됨  
  - `let PAT = EXPR;` 형태로, 보기보단 더 강력함  
  - 패턴 매칭과 결합하여 편리한 기능을 제공   
    - `let (a, b) = (5, 10);`  
    - `let maybe_string: Option&lt;String&gt; = ..;`  
    - `let Some(value) = maybe_string else { panic!("die horribly")};`  
- `const`는 컴파일 타임에 계산되어 컴파일된 코드에 직접 포함되는 상수  
  - `const MY_VAR: &str = "heyyyyyyyy man";` `const SECRET: i32 = 0x1234;`  
  - `const IDENT: TYPE = EXPR;` 형태로, 타입을 명시해야 하며 패턴을 사용할 수 없음  
  
### 헷갈리게 하는 것   
- `const`는 선언 순서에 상관없이 사용 가능함 (hoisting)  
```  
// X가 Y 뒤에 정의되어 있어도 컴파일됨  
const Y: i32 = X + X;  
const X: i32 = 5;  
```  
- 함수 내부에서도 선언 가능하며, 그 상태에서도 호이스팅도 가능   
```  
fn oh_boy() -> i32 {  
	return X;  
	const X: i32 = 5;  
	// ^ 컴파일 되며 동작함. 워닝 없음!  
}  
```  
- 자바스크립트 출신으로 이제 막 Rust를 배우는 프로그래머와 함께 작업하는 경우, 이 기능은 그들을 당황하게 만들 수 있는 훌륭한 기능임   
- 훌륭한 기능의 무해한 결과인데, 이제 해로운 결과를 작성해보기로 함   
  
### Rust의 Match   
```rs  
// let PAT = EXPR;  
let x = 5;  
  
// 이 경우, `x`는 패턴임. `5`를 `x`에 넣을 수 있는지 확인함  
// 이 패턴은 항상 매치됨 -- 항상 5를 `x`라는 변수에 넣을 수 있음   
  
// 모든 패턴이 반드시 매치될 필요는 없음. 예를 들어:  
let (5, x) = (a, b);  
// 여기서 표현식은 a == 5인 경우에만 패턴과 "매치"  
//  
// 이를 "반박 가능한(refutable)" 패턴이라고 함  
//  
// `let` 선언에서, 반박 가능한 패턴은 "거부된(refused)" 경우를 처리해야 함:  
let (5, x) = (a, b) else { panic!() };  
//  
// ...그렇지 않으면 "조건부로 존재하는(conditionally existing)" 변수를 갖게 될 수 있는데, 이는 좋지 않음  
```  
  
- 그럼 `match`에 대해 알아봅시다. match는 무엇일까요?  
  
```rs  
// match는 패턴과 매치될 경우 수행할 작업의 목록  
//  
// match EXPR {  
//    PAT => EXPR  
//    PAT => EXPR  
//    ..  
// }  
  
match (a, b) {  
	(5, x) => {  
		// 만약 (a,b)가 (5,x)와 매치되면, 이 블록이 실행됨  
	},  
	(x, 5) => {  
		// 같은 방식으로: 만약 (a,b)가 (x, 5)와 매치되면..  
	},  
	(x, y) => {  
		// 그리고 이것은 "모든 것을 잡아내는" 패턴으로, let (x,y) = (a,b)가 동작하는 방식과 같음  
	}  
}  
```  
  
### 고통을 줘 봅시다  
- 사람들을 혼란스럽게 하는 것도 재미있지만, 완전한 불행과 실제 버그를 야기하는 것은 어떨까?  
- 내가 보기엔 이것이 Rust의 **가장 미묘한** 문법임:  
  - 이 글에서 가장 흥미로운 한 줄 : **Rust의 가장 미묘한 문법은 상수 자체가 패턴이라는 것**  
- 이 문법은 매칭 주변에 몇 가지 좋은 ergonomic을 추가함:  
  
```rs  
let input: i32 = ..;  
  
const GOOD: i32 = 1;  
const BAD: i32 = 2;  
  
match input {  
	// 이것은 input == GOOD인지 확인. 왜냐하면 GOOD은 상수이기 때문  
	GOOD => println!("input was 1"),  
	// 이것은 input == BAD인지 확인. 왜냐하면 BAD는 상수이기 때문.  
	BAD => println!("input was 2"),  
	// 이것은 otherwise = input으로 정의하고, 항상 매치됨...  
	otherwise => println!("input was {otherwise}"),  
}  
```  
  
그러나 상수를 대문자로 쓰는 것은 단순히 관례일 뿐. 그렇게 하지 말라는 컴파일러 경고일 뿐임.  
  
```rs  
const good: i32 = 1;  
const bad: i32 = 2;  
match input {  
	// 음...  
	good => {},  
	bad => {},  
	otherwise => {},  
}  
```  
  
이제 우리는 동일해 보이는 세 개의 분기를 가지고 있지만, 그것들이 하는 일은 해당 이름의 상수가 존재하는지에 따라 달라짐!  
더 나빠져 봅시다. 아래에선 어떤 일이 일어날까?  
  
```rs  
const GOOD: i32 = 1;  
match input {  
	// 오타...  
	GOD => println!("input was 1"),  
	otherwise => println!("input was not 1")  
}  
```  
  
여기서는 컴파일러 경고가 나타나겠지만, 이 코드는 항상 `input was 1`을 출력할 것  
또는 좀 더 현실적으로:  
  
```rs  
// 이런, 실수로 이 임포트를 주석 처리하거나 삭제했음  
// use crate::{SOME_GL_CONSTANT, OTHER_THING}  
  
// 이런!  
match value {  
	SOME_GL_CONSTANT => ..,  
	OTHER_THING => ..,  
	_ => ..,  
}  
```  
  
이것은 사람들을 혼란스럽게 함. 특히 그들이 열거형으로 멋진 것들을 시도할 때 더욱.  
  
```rs  
enum MyEnum {  
	A, B, C  
}  
  
// 보통은 이렇게 작성함  
match value {  
	MyEnum::A => ..,  
	MyEnum::B => ..,  
	MyEnum::C => ..,  
}  
  
// 하지만 이렇게 작성할 수도 있음  
use MyEnum::*;  
match value {  
	A => {},  
	B => {},  
	C => {}  
}  
```  
  
```rs  
// 그리고 나서, 만약 MyEnum을 변경한다면...  
enum MyEnum { A, B, D, E };  
use MyEnum::*;  
  
// 이것은 여전히 컴파일됨!  
match value {  
	A => {},  
	B => {},  
	C => {},  
}  
  
// `C`는 이제 "모든 것을 잡아내는" 패턴이 됨. 왜냐하면 `C`와 같은 것이 범위 내에 없기 때문.  
// 여러분은 let C = value를 하고 있는 것이고, 이는 항상 매치됨!!!  
```  
  
[Clippy](https://github.com/rust-lang/rust-clippy)는 이렇게 하지 말라고 경고하는 많은 규칙을 가지고 있음. 왜냐하면 이것이 사람들을 항상 혼란스럽게 하기 때문.  
그러나 이것은 더욱 혼란스럽게 만들 수 있음:  
  
```rs  
// x를 5에 irrefutably 바인딩...  
let x = 5;  
  
// ...잠깐만요...  
const x: i32 = 4;  
```  
  
이 코드는 컴파일되지 않음. 왜냐하면 `const x`는 패턴이고, 상수는 호이스팅되며, 이제 이 코드는 다음과 같이 평가되기 때문:  
  
```rs  
let 4 = 5;  
  
// error[E0005]: refutable pattern in local binding  
//  --> src/main.rs:3:5  
//   |  
// 3 | let x = 5;  
//   |     ^  
//   |     |  
//   |     패턴 `i32::MIN..=3_i32`와 `5_i32..=i32::MAX`가 커버되지 않음  
//   |     누락된 패턴은 `x`가 새로운 변수가 아닌 상수 패턴으로 해석되기 때문에 커버되지 않음  
//   |     도움말: 대신 변수를 도입하세요: `x_var`  
//   |  
//   = 참고: `let` 바인딩은 "irrefutable pattern"을 필요로 함. 예를 들어 `struct`나 하나의 variant만 가진 `enum`처럼  
```  
  
"expr이 4와 같다"는 반박할 수 없는 매치가 아니며, 그렇지 않은 경우를 처리하지 않음  
  
### 주위 모두를 짜증나게 만들기   
  
```rs  
// `maybe`가 Option<&str>이라고 가정. 어떤 텍스트일 수도 있고, None일 수도 있음.  
let maybe_username: Option<&str> = ..;  
  
// 이것은 한 줄 매치에서 Rust의 일반적인 패턴. 이것이 Some(..)과 매치한다면 우리는 그 문자열로 무언가를 할 수 있음.  
if let Some(username) = maybe_username {  
	// 그래서 이 코드는 username이 존재하면 실행됨...  
	return username.to_uppercase();  
}  
  
// 그런데 말이죠... 이제 그 코드는 'username'이 Some("hey")과 매치할 때만 실행됨  
const username: &str = "hey";  
```  
  
상수 호이스팅과 상수가 패턴이라는 사실의 조합은 여러분이 수수께끼 같은 Rust 코드를 작성할 수 있게 해줌  
  
### 이것은 실제 문제는 아님  
- 현실적으로, 이것이 혼란스러울 수 있는 유일한 이유는 여러분이 `let UPPERCASE`와 `const lowercase`를 작성할 수 있다는 것  
- 만약 대문자로 시작하는 변수를 만드는 것이 lint 오류였다면, 혼란은 일어나지 않을 것  
  - 열거형 variant나 상수와 매치하려고 할 때 실수로 무언가를 바인딩할 수는 없을 것이기에   
- 하지만 분명히 하자면, 이것은 단지 언어의 재미있는 특이점일 뿐  
  
```rs  
macro_rules! f {  
  ($cond: expr) => {  
    if let Some(x) = $cond {  
      println!("i am some == {x}!");  
    } else {  
      println!("i am none");  
    }  
  }  
}  
  
fn main() {  
    f!(Some(100));  
  
    {  
        f!(Some(100));  
        return;  
  
        const x: i32 = 5;  
    }  
}  
```

## Comments



### Comment 30753

- Author: sunrabbit
- Created: 2024-11-05T12:54:52+09:00
- Points: 2

사실 큰 문제는 아닌게 웬만한 개발 환경에는 랭귀지 서버가 있고  
거기서 다 추론해서 보여주기때문이죠  
  
러스트로버 랭귀지 서버의 기반인 rust-analyzer는 꽤나 강력한도구거든요

### Comment 30754

- Author: sunrabbit
- Created: 2024-11-05T12:55:21+09:00
- Points: 2
- Parent comment: 30753
- Depth: 1

그냥 어느 언어든 있는 다크패턴들을 모아다가  
이거는 헷갈림을 유발할 수 있음!  
  
이런 느낌의 글인거죠

### Comment 30751

- Author: kayws426
- Created: 2024-11-05T12:23:06+09:00
- Points: 1

헐... 스럽네요. 러스트는 이걸 어찌할 계획일까요?
