튀르키예 2주 여행 후기
[튀르키예 2주 여행 후기]
나는 친한 동생과 함께 2024/01/06 ~ 2024/01/20 일정으로 튀르키예 여행을 다녀왔다. 동서양의 교차점이자 어릴때부터 막연히 가고 싶었던 튀르키예 여행을 행복하게, 무사히 마무리 한 후 다음 여행자들을 위한 후기를 남긴다.
나는 친한 동생과 함께 2024/01/06 ~ 2024/01/20 일정으로 튀르키예 여행을 다녀왔다. 동서양의 교차점이자 어릴때부터 막연히 가고 싶었던 튀르키예 여행을 행복하게, 무사히 마무리 한 후 다음 여행자들을 위한 후기를 남긴다.
신생사이트는 일단 대중에게 노출되는 것이 무엇보다 중요하다. 이는 마케팅의 영역일 것이다.
이 점에 대해 프론트엔드 개발자로 기여 할 수 있는 부분은 SEO
관련 부분일 것이다.
검색엔진은 SEO가 잘 된 사이트에 더 높은 점수를 부여하고 사용자에게 더 잘 노출될 수 있게 한다.
SEO는 사실 복잡한 개념은 아니라고 생각한다.
이정도면 충분하다고 생각한다. 과거에는 백링크의 수가 검색 결과 순위를 결정하는 주요 요인이었지만, 최근에는 실제 트래픽이 더욱 중요해졌다.
프론트에서 할 수 있는 부분을 놓치지 말고 진행하자.
여러가지 상황이 있었다.
열심히 살고 있는 것 같은데, 왜 이렇게 할게 늘기만 하는지 ㅋㅋ... 효율성이 중요한 시점이다.
간만에 포스트를 쓰는 이유는 심란해서이다.
솔라나 프로그램을 작성할 목적으로, 러스트 언어를 두달 정도 학습했었다.
이제 프로그램 분석하고 작성해보는 단계를 진행하고 있는데, 갑자기 왠 FTX 벼락 ㅠㅠ 인지 솔라나가 망하게 생겼다...
Rust + 스마트 컨트랙트를 둘다 만족시켜주는 선택지는 이렇게 사라지고 말았다.
자산도 다 코인으로 바꿔놨는데 하 인생...
2022년 마지막까지 열심히 살고, KRW 채굴 역량이나 올려야겠다!
러스트의 신뢰성에 대한 약속은 에러 처리에도 확장되어 있습니다.
에러는 소프트웨어에서 피할 수 없는 현실이며, 따라서 러스트는 무언가 잘못되었을 경우에 대한 처리를 위한 몇 가지 기능을 갖추고 있습니다.
러스트는 에러를 두 가지 범주로 묶습니다:
대부분의 언어들은 이 두 종류의 에러를 분간하지 않으며 예외 처리(exception)
와 같은 메카니즘을 이용하여 같은 방식으로 둘 다 처리합니다.
러스트는 예외 처리 기능이 없습니다.
Result<T, E>
값과panic!
매크로를 가지고 있습니다.이번 장에서는 panic!
을 호출하는 것을 먼저 다룬 뒤, Result<T, E>
값을 반환하는 것에 대해 이야기 하겠습니다.
추가로, 에러로부터 복구을 시도할지 아니면 실행을 멈출지를 결정할 때 고려할 것에 대해 탐구해 보겠습니다.
panic!
과 함께하는 복구 불가능한 에러panic!
매크로를 가지고 있습니다.panic!
에 응하여 스택을 되감거나 그만두기기본적으로,
panic!
이 발생하면, 프로그램은 되감기(unwinding) 를 시작하는데, 이는 러스트가 패닉을 마주친 각 함수로부터 스택을 거꾸로 훑어가면서 데이터를 제거한다는 뜻이지만, 이 훑어가기 및 제거는 일이 많습니다.다른 대안으로는 즉시 그만두기(abort) 가 있는데, 이는 데이터 제거 없이 프로그램을 끝내는 것입니다. 프로그램이 사용하고 있던 메모리는 운영체제에 의해 청소될 필요가 있을 것입니다. 여러분의 프로젝트 내에서 결과 바이너리가 가능한 작아지기를 원한다면, 여러분의 Cargo.toml 내에서 적합한
[profile]
섹션에panic = 'abort'
를 추가함으로써 되감기를 그만두기로 바꿀 수 있습니다.예를 들면, 여러분이 릴리즈 모드 내에서는 패닉 상에서 그만두기를 쓰고 싶다면, 다음을 추가하세요:
[profile.release] panic = 'abort'
단순한 프로그램 내에서 panic!
호출을 시도해 봅시다:
panic!("crash and burn");
panic!
의 호출이 마지막 세 줄의 에러 메세지를 야기합니다.panic!
매크로 호출을 보게 됩니다.panic!
호출이 우리가 호출한 코드 내에 있을 수도 있습니다.panic!
매크로가 호출된 다른 누군가의 코드일 것이며, 궁극적으로 panic!
을 이끌어낸 것이 우리 코드 라인이 아닐 것입니다.panic!
호출이 발생된 함수에 대한 백트레이스(backtrace)를 사용할 수 있습니다.panic!
백트레이스 사용하기다른 예를 통해서, 우리 코드가 직접 매크로를 호출하는 대신 우리 코드의 버그 때문에 panic!
호출이 라이브러리로부터 발생될 때는 어떻게 되는지 살펴봅시다.
이러한 상황에서 C와 같은 다른 언어들은 여러분이 원하는 것이 아닐지라도, 여러분이 요청한 것을 정확히 주려고 시도할 것입니다: 여러분은 벡터 내에 해당 요소와 상응하는 위치의 메모리에 들어 있는 무언가를 얻을 것입니다. 설령 그 메모리 영역 이 벡터 소유가 아닐지라도 말이죠.
이러한 것을 버퍼 오버리드(buffer overread) 라고 부르며, 만일 어떤 공격자가 읽도록 허용되어선 안 되지만 배열 뒤에 저장된 데이터를 읽어낼 방법으로서 인덱스를 다룰 수 있게 된다면, 이는 보안 취약점을 발생시킬 수 있습니다.
여러분의 프로그램을 이러한 종류의 취약점으로부터 보호하기 위해서, 여러분이 존재하지 않는 인덱스 상의 요소를 읽으려 시도한다면, 러스트는 실행을 멈추고 계속하기를 거부할 것입니다. 한번 시도해 봅시다:
libcollections/vec.rs
를 가리키고 있습니다.Vec<T>
의 구현 부분입니다.v
에 []
를 사용할 때 실행되는 코드는 libcollections/vec.rs
안에 있으며, 그곳이 바로 panic!
이 실제 발생한 곳입니다.RUST_BACKTRACE
환경 변수를 설정하여 에러의 원인이 된 것이 무엇인지 정확하게 백트레이스할 수 있다고 말해주고 있습니다.백트레이스 (backtrace)
란 어떤 지점에 도달하기까지 호출해온 모든 함수의 리스트를 말합니다.환경 변수 RUST_BACKTRACE
가 설정되었을 때 panic!
의 호출에 의해 발생되는 백트레이스 출력
cargo build
나 cargo run
을 --release
플래그 없이 실행했을 때 기본적으로 활성화됩니다.여러분의 코드가 추후 패닉에 빠졌을 때, 여러분의 특정한 경우에 대하여 어떤 코드가 패닉을 일으키는 값을 만드는지와 코드는 대신 어떻게 되어야 할지를 알아낼 필요가 있을 것입니다.
우리는 panic!
으로 다시 돌아올 것이며 언제 panic!
을 써야 하는지, 혹은 쓰지 말아야 하는지에 대해 이 장의 뒷부분에서 알아보겠습니다. 다음으로 Result
를 이용하여 에러로부터 어떻게 복구하는지를 보겠습니다.
Result
와 함께하는 복구 가능한 에러대부분의 에러는 프로그램을 전부 멈추도록 요구될 정도로 심각하지는 않습니다. 종종 어떤 함수가 실패할 때는, 우리가 쉽게 해석하고 대응할 수 있는 이유에 대한 것입니다.
예를 들어, 만일 우리가 어떤 파일을 여는데 해당 파일이 존재하지 않아서 연산에 실패했다면, 프로세스를 멈추는 대신 파일을 새로 만드는 것을 원할지도 모릅니다
enum Result<T, E> {
Ok(T),
Err(E),
}
T
와 E
는 제네릭 타입 파라미터입니다;T
는 성공한 경우에 Ok
variant 내에 반환될 값의 타입을 나타내고 E
는 실패한 경우에 Err
variant 내에 반환될 에러의 타입을 나타내는 것이라는 점입니다.Result
가 이러한 제네릭 타입 파라미터를 갖기 때문에, 우리가 반환하고자 하는 성공적인 값과 에러 값이 다를 수 있는 다양한 상황 내에서 표준 라이브러리에 정의된 Result
타입과 함수들을 사용할 수 있습니다.실패할 수도 있기 때문에 Result
값을 반환하는 함수를 호출해 봅시다
use std::fs::File;
fn main() {
let f = File::open("hello.txt");
}
File::open
이 Result
를 반환하는지 어떻게 알까요?
표준 라이브러리 API 문서를 찾아보거나, 컴파일러에게 물어볼 수 있습니다!
File::open
함수의 반환 타입이 Result<T, E>
여기서 제네릭 파라미터 T
는 성공값의 타입인 std::fs::File
로 채워져 있는데,
이것은 파일 핸들입니다.
에러에 사용되는 E
의 타입은 std::io::Error
입니다.
이 반환 타입은 File::open
을 호출하는 것이 성공하여 우리가 읽거나 쓸 수 있는 파일 핸들을 반환해 줄 수도 있다는 뜻입니다.
File::open
함수는 우리에게 성공했는지 혹은 실패했는지를 알려주면서 동시에 파일 핸들이나 에러 정보 둘 중 하나를 우리에게 제공할 방법을 가질 필요가 있습니다.
바로 이러한 정보가 Result
열거형이 전달하는 것과 정확히 일치합니다.
File::open
이 성공한 경 우, 변수 f
가 가지게 될 값은 파일 핸들을 담고 있는 Ok
인스턴스가 될 것입니다.
실패한 경우, f
의 값은 발생한 에러의 종류에 대한 더 많은 정보를 가지고 있는 Err
의 인스턴스가 될 것입니다.
use std::fs::File;
fn main() {
let f = File::open("hello.txt");
let f = match f {
Ok(file) => file,
Err(error) => {
panic!("There was a problem opening the file: {:?}", error)
},
};
}
Listing 9-4: match
표현식을 사용하여 발생 가능한 Result
variant들을 처리하기
Option
열거형과 같이 Result
열거형과 variant들은 프렐루드(prelude)로부터 가져와진다는 점을 기억하세요. ??match
의 각 경우에 대해서 Ok
와 Err
앞에 Result::
를 특정하지 않아도 됩니다.여기서 우리는 러스트에게 결과가 Ok
일 때에는 Ok
variant로부터 내부의 file
값을 반환하고, 이 파일 핸들 값을 변수 f
에 대입한다고 말해주고 있습니다.
match
이후에는 읽거나 쓰기 위해 이 파일 핸들을 사용할 수 있습니다.
File::open
이 실패한 이유가 무엇이든 간에 panic!
을 일으킬 것입니다.File::open
이 실패한 것이라면, 새로운 파일을 만들어서 핸들을 반환하고 싶습니다.File::open
이 실패한 거라면, 예를 들어 파일을 열 권한이 없어서라면, Listing 9-4에서 했던 것과 마찬가지로 panic!
을 일으키고 싶습니다.use std::fs::File;
use std::io::ErrorKind;
fn main() {
let f = File::open("hello.txt");
let f = match f {
Ok(file) => file,
Err(ref error) if error.kind() == ErrorKind::NotFound => {
match File::create("hello.txt") {
Ok(fc) => fc,
Err(e) => {
panic!(
"Tried to create file but there was a problem: {:?}",
e
)
},
}
},
Err(error) => {
panic!(
"There was a problem opening the file: {:?}",
error
)
},
};
}
Err
variant 내에 있는 File::open
이 반환하는 값의 타입은 io::Error
인데, 이는 표준 라이브러리에서 제공하는 구조체입니다.kind
메소드를 제공하는데 이를 호출하여 io::ErrorKind
값을 얻을 수 있습니다.io::ErrorKind
는 io
연산으로부터 발생할 수 있는 여러 종류의 에러를 표현하는 variant를 가진, 표준 라이브러리에서 제공하는 열거형입니다.ErrorKind::NotFound
인데, 이는 열고자 하는 파일이 아직 존재하지 않음을 나타냅니다.if error.kind() == ErrorKind::NotFound
는 매치 가드(match guard) 라고 부릅니다:ref
가 필요하며 그럼으로써 error
가 가드 조건문으로 소유권 이동이 되지 않고 그저 참조만 됩니다.
&
대신 ref
이 사 용되는 이유는 18장에서 자세히 다룰 것입니다.&
는 참조자를 매치하고 그 값을 제공하지만, ref
는 값을 매치하여 그 참조자를 제공합니다.error.kind()
에 의해 반환된 값이 ErrorKind
열거형의 NotFound
variant인가 하는 것입니다.match
의 마지막 갈래는 똑같이 남아서, 파일을 못 찾는 에러 외에 다른 어떤 에러에 대해서도 패닉을 일으킵니다.unwrap
과 expect
match
의 사용은 충분히 잘 동작하지만, 살짝 장황하기도 하고 의도를 항상 잘 전달하는 것도 아닙니다.Result<T, E>
타입은 다양한 작업을 하기 위해 정의된 수많은 헬퍼 메소드를 가지고 있습니다.unwrap
이라 부르는 메소드는 Listing 9-4에서 작성한 match
구문과 비슷한 구현을 한 숏컷 메소드입니다.Result
값이 Ok
variant라면, unwrap
은 Ok
내의 값을 반환할 것입니다.Result
가 Err
variant라면, unwrap
은 우리를 위해 panic!
매크로를 호출할 것입니다. 아래에 unwrap
이 작동하는 예가 있습니다:use std::fs::File;
fn main() {
let f = File::open("hello.txt").unwrap();
}
또 다른 메소드인 expect
는 unwrap
과 유사한데, 우리가 panic!
에러 메세지를 선택할 수 있게 해줍니다. unwrap
대신 expect
를 이용하고 좋은 에러 메세지를 제공하는 것은 여러분의 의도를 전달해주고 패닉의 근원을 추적하는 걸 쉽게 해 줄 수 있습니다. expect
의 문법은 아래와 같이 생겼습니다:
use std::fs::File;
fn main() {
let f = File::open("hello.txt").expect("Failed to open hello.txt");
}
expect
는 unwrap
과 같은 식으로 사용됩니다:
panic!
매크로를 호출하는 것이죠.expect
가 panic!
호출에 사용하는 에러 메세지는 unwrap
이 사용하는 기본 panic!
메세지보다는 expect
에 넘기는 파라미터로 설정될 것입니다.unwrap
을 사용하면, 정확히 어떤 unwrap
이 패닉을 일으켰는지 찾기에 좀 더 많은 시간이 걸릴 수 있는데, 그 이유는 패닉을 호출하는 모든 unwrap
이 동일한 메세지를 출력하기 때문입니다.실패할지도 모르는 무언가를 호출하는 구현을 가진 함수를 작성할 때, 이 함수 내에서 에러를 처리하는 대신, 에러를 호출하는 코드 쪽으로 반환하여 그쪽에서 어떻게 할지 결정하도록 할 수 있습니다.
이는 에러 전파하기로 알려져 있으며, 에러가 어떻게 처리해야 좋을지 좌우해야 할 상황에서, 여러분의 코드 내용 내에서 이용 가능한 것들보다 더 많은 정보와 로직을 가지고 있을 수도 있는 호출하는 코드 쪽에 더 많은 제어권을 줍니다.
use std::io;
use std::io::Read;
use std::fs::File;
fn read_username_from_file() -> Result<String, io::Error> {
let f = File::open("hello.txt");
let mut f = match f {
Ok(file) => file,
Err(e) => return Err(e),
};
let mut s = String::new();
match f.read_to_string(&mut s) {
Ok(_) => Ok(s),
Err(e) => Err(e),
}
}
Listing 9-6: match
를 이용하여 호출 코드 쪽으로 에러를 반환하는 함수
함수의 반환 타입부터 먼저 살펴봅시다:
Result<String, io::Error>
. 이는 함수가 Result<T, E>
타입의 값을 반환하는데 제네릭 파라미터 T
는 구체적 타입(concrete type)인 String
로 채워져 있고, 제네릭 타입 E
는 구체적 타입인 io::Error
로 채워져 있습니다.String
을 담은 값을 받을 것입니다
io::Error
의 인스턴스를 담은 Err
값을 받을 것입니다.
io::Error
를 선택했는데,File::open
함수와 read_to_string
메소드 말이죠.File::open
함수를 호출하면서 시작합니다.match
와 유사한 식으로 match
을 이용해서 Result
값을 처리하는데, Err
경우에 panic!
을 호출하는 대신 이 함수를 일찍 끝내고 File::open
으로부터의 에러 값을 마치 이 함수의 에러 값인 것처럼 호출하는 쪽의 코드에게 전달합니다.File::open
이 성공하면, 파일 핸들을 f
에 저장하고 계속합니다.s
에 새로운 String
을 생성하고 파일의 콘텐츠를 읽어 s
에 넣기 위해 f
에 있는 파일 핸들의 read_to_string
메소드를 호출합니다.File::open
가 성공하더라도 read_to_string
메소드가 실패할 수 있기 때문에 이 함수 또한 Result
를 반환합니다.Result
를 처리하기 위해서 또 다른 match
가 필요합니다:read_to_string
이 성공하면, 우리의 함수가 성공한 것이고, 이제 s
안에 있는 파일로부터 읽어들인 사용자 이름을 Ok
에 싸서 반환합 니다.read_to_string
이 실패하면, File::open
의 반환값을 처리했던 match
에서 에러값을 반환하는 것과 같은 방식으로 에러 값을 반환합니다.return
이라 말할 필요는 없는데, 그 이유는 이 함수의 마지막 표현식이기 때문입니다.Ok
값 혹은 io::Error
를 담은 Err
값을 얻는 처리를 하게 될 것입니다.Err
값을 얻었다면, 예를 들면 panic!
을 호출하여 프로그램을 종료시키는 선택을 할 수도 있고, 기본 사용자 이름을 사용할 수도 있으며, 혹은 파일이 아닌 다른 어딘가에서 사용자 이름을 찾을 수도 있습니다.러스트에서 에러를 전파하는 패턴은 너무 흔하여 러스트에서는 이를 더 쉽게 해주는 물음표 연산자 ?
를 제공합니다.
?
Listing 9-7은 Listing 9-6과 같은 기능을 가진 read_username_from_file
의 구현을 보여주는데, 다만 이 구현은 물음표 연산자를 이 용하고 있습니다:
use std::io;
use std::io::Read;
use std::fs::File;
fn read_username_from_file() -> Result<String, io::Error> {
let mut f = File::open("hello.txt")?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}
String
을 만들어 s
에 넣는 부분을 함수의 시작 부분으로 옮겼습니다;f
변수를 만드는 대신, File::open("hello.txt")?
의 결과 바로 뒤에 read_to_string
의 호출을 연결시켰습니다.read_to_string
호출의 끝에는 여전히 ?
가 남아있고, File::open
과 read_to_string
이 모두 에러를 반환하지 않고 성공할 때 s
안의 사용자 이름을 담은 Ok
를 여전히 반환합니다.?
는 Result
를 반환하는 함수에서만 사용될 수 있습니다