이번 포스팅에서는 slice에 대해서 다뤄보도록 하겠다.
pub fn run() {
let a = [3, 2, 9, -1, 8];
println!("{}", max(&a));
let v = vec![3, 2, 7, 6, 8];
println!("{}", max(&v[1..4]));
println!("{}", max(&v[1..=4]));
println!("{}", max2(&v[1..=4]));
if let Some(x) = find_even(&v) {
println!("{x}");
} else {
println!("no even numbers");
}
// example of use of an iterator
let v2 = a.into_iter().collect::<Vec<_>>();
println!("{v2:?}");
}
코드 분석
이 코드는 배열과 벡터에서 최대값을 찾는 함수와, 벡터에서 첫 번째 짝수를 찾는 함수를 예시로 보여주고 있다.
max(&a)
: 배열a
에서 최대값을 찾아서 출력한다.- 여기서 a대신 &a를 넣는 이유는 함수 내에서 배열의 소유권을 넘기지 않고 참조만 전달하기 위해서이다. 만약 a만 넣으면, 배열 a의 소유권이 max 함수로 넘어가고, 함수가 끝난 뒤에는 a를 더 이상 사용할 수 없게 되기 때문이다.
- &a를 넣어서 참조를 전달하면, max 함수는 a 배열에 대한 읽기 권한만 얻으므로 원본 배열에는 아무런 영향을 미치지 않는다. 그래서 max 함수가 끝난 뒤에도 여전히 a를 사용할 수 있다.
max(&v[1..4])
: 벡터v
의 인덱스 1에서 3까지 범위에서 최대값을 찾아서 출력한다.max(&v[1..=4])
: 벡터v
의 인덱스 1에서 4까지 범위에서 최대값을 찾아서 출력한다.max2(&v[1..=4])
: 동일한 작업을 하는데,max2
라는 다른 함수를 사용한다.find_even(&v)
: 벡터v
에서 첫 번째 짝수를 찾는다. 찾으면 그 값을 출력하고, 없으면 "no even numbers"를 출력한다.let v2 = a.into_iter().collect::<Vec<_>>();
: 배열a
를 벡터v2
로 변환한다.
백터와 배열
let a = [3, 2, 9, -1, 8];
let v = vec![3, 2, 7, 6, 8];
a
는 배열(array)이고, v
는 벡터(vector)다. 둘은 Rust에서 제공하는 서로 다른 컬렉션 타입이다.
-
크기 고정 vs 동적
- 배열
a
는 크기가 고정되어 있다. 즉, 5개의 정수를 저장할 수 있고, 이 크기는 변경할 수 없다. - 벡터
v
는 동적이다. 원하는 만큼 요소를 추가하거나 제거할 수 있다.
- 배열
-
타입
- 배열
a
는[i32; 5]
타입을 가진다. 크기 정보도 타입에 포함된다.-
[i32; 5]
타입은 Rust에서 배열을 표현하는 방식 중 하나다. 이 타입은i32
타입의 원소를 정확히 5개 가질 수 있는 배열을 나타낸다. -
i32
는 32비트 정수를 나타내는 타입이다. -
5
는 배열의 크기나 길이를 나타낸다. 이 배열은 정확히 5개의i32
원소를 가질 수 있다. -
예를 들어,
let a = [1, 2, 3, 4, 5];
같은 코드에서a
는[i32; 5]
타입을 가질 것이다. -
이렇게 타입 정보에 배열의 크기까지 명시되는 것은 배열이 고정된 크기를 가진다는 것을 명확히 보여준다.
-
- 벡터
v
는Vec<i32>
타입을 가진다. 크기 정보는 타입에 포함되지 않는다.- 크기가 동적으로 변할 수 있는 i32 타입 배열을 나타낸다.
- 배열
-
표준 라이브러리
- 배열은 Rust의 내장 타입이다.
- "배열은 Rust의 내장 타입이다"라는 문장은, 배열이 Rust 언어 자체에 내장되어 있는 기본 타입이라는 의미다. 즉, 특별한 라이브러리를 추가하지 않아도 사용할 수 있다.
- 벡터는 표준 라이브러리에서 제공된다.
- "벡터는 표준 라이브러리에서 제공된다"는, 벡터는 Rust의 표준 라이브러리(std)에 정의되어 있는 타입이라는 것을 의미한다. 표준 라이브러리는 Rust 설치와 함께 제공되긴 하지만, 기본 언어 구조에는 포함되어 있지 않다.
- 배열은 Rust의 내장 타입이다.
-
유연성
- 배열은 크기가 고정되어 있어 유연성이 떨어진다.
- 벡터는
push
,pop
같은 메서드를 통해 동적으로 크기를 변경할 수 있다.
그래서, 어떤 상황에서는 고정된 크기의 배열이 적합하고, 어떤 상황에서는 유연한 크기의 벡터가 적합하다.
if let Some(x) = find_even(&v)
if let
문은 Rust에서 특정 패턴에 맞는 값이 있을 때만 블록 안의 코드를 실행하게 해준다. Option<T>
타입의 값(여기서는 find_even(&v)
의 반환값)이 Some(x)
일 경우에만 if let
블록 내의 코드가 실행된다.
-
Option<T>
는 Rust에서 어떤 값이 있을 수도, 없을 수도 있을 때 사용하는 타입이다. 이게 무슨 말이냐면, 예를 들어 사과 상자에서 빨간 사과를 찾으려고 할 때, 빨간 사과가 있을 수도 있고 없을 수도 있다. 빨간 사과가 있다면 그걸 가져오고, 없다면 빈 손으로 돌아온다.-
Some(사과)
는 "사과를 찾았다!"라는 뜻이고, -
None
은 "사과가 없어서 빈 손이다"라는 뜻이다. -
이제
if let Some(x) = find_even(&v)
이 코드를 생각해보자. 이 코드는 "상자 안에 빨간 사과가 있으면 그걸x
에 넣어줘" 같은 뜻이다.find_even(&v)
가 짝수를 찾으면Some(짝수)
를 반환하고, 그 값이x
에 들어가게 된다. 그리고if let
뒤에 있는 코드가 실행된다. 만약 짝수를 못 찾으면,None
이 반환되고,if let
뒤에 있는 코드는 실행되지 않는다. -
간단하게 말하면,
if let
은 "만약 이런 값이 있다면, 그 값을 가져와서 이 일을 해줘"라는 뜻이다.
-
find_even(&v)
함수는 아마도 벡터 v
에서 첫 번째 짝수를 찾아 Some(짝수)
를 반환하거나, 짝수가 없을 경우 None
을 반환할 것이다.
Some(x)
가 반환된다면,x
는 그 짝수 값이고,if let
블록 내의 코드가 실행된다.None
이 반환된다면,if let
블록 내의 코드는 실행되지 않는다.
간단한 예를 들면,
find_even(&v)
가Some(2)
를 반환한다면,x
는2
가 되고 블록 내의 코드가 실행된다.find_even(&v)
가None
을 반환한다면, 블록 내의 코드는 실행되지 않는다.
이렇게 if let
을 사용하면 match
문을 사용하는 것보다 간결하게 특정 패턴에 대해서만 코드를 실행할 수 있다.
동작
가령 v라는 배열이 [1, 3, 4, 7]이라고 하자. 이 배열에서 짝수를 찾는 find_even(&v) 함수를 실행하면, 여기서 짝수는 4이다.
그래서 find_even(&v)의 반환값은 Some(4)가 된다.
이제 if let Some(x) = find_even(&v)을 실행하면 이렇게 동작한다:
find_even(&v)를 실행해서 Some(4)를 가져온다. Some(4)에서 4를 꺼내서 x에 저장한다. 그리고 if let 블록 내의 코드가 실행된다. 여기서 x는 4이다. 만약 v 배열이 [1, 3, 5, 7]이라면, 짝수가 없다. 이 경우 find_even(&v)는 None을 반환한다.
그러면 if let Some(x) = find_even(&v)이 동작하는 과정은:
find_even(&v)를 실행해서 None을 가져온다. 값이 None이므로, if let 블록 내의 코드는 실행되지 않는다.
Some(4)에서 4를 왜, 그리고 어떻게 꺼내는가?
Some(4)
에서 4
를 꺼내는 과정은 Rust의 if let
문법을 통해 이루어진다. 이 문법은 Option<T>
타입의 값이 Some
인 경우에만 코드 블록을 실행하고, 그 Some
안에 들어있는 값을 변수에 바인딩한다.
코드에서 if let Some(x) = find_even(&v)
를 보면:
find_even(&v)
의 반환값이Some(4)
라고 가정하자.if let Some(x) = Some(4)
이 실행된다.- 여기서
Some(x)
패턴은Some(4)
와 일치하므로,x
에4
가 바인딩된다. - 이후
if let
블록 내의 코드가 실행되고, 여기서x
는4
의 값을 가진다.
if let
문법은 패턴 매칭을 통해 Some
안의 값을 꺼내서 변수에 저장하는 역할을 한다. 이렇게 꺼낸 값은 if let
블록 내에서 사용할 수 있다.
// &[T] is a fat pointer: ptr to beginning + length
fn max(s: &[i32]) -> i32 {
let mut max = s[0];
for i in 1..s.len() {
if s[i] > max {
max = s[i];
}
}
max
}
fn max2(s: &[i32]) -> i32 {
let mut max = s[0];
for x in s {
// x has type &i32
if *x > max {
max = *x;
}
}
max
}
이 두 함수 max
와 max2
는 배열 또는 벡터의 참조(&[i32]
)를 인자로 받아서 그 중에서 가장 큰 값을 찾는 역할을 한다. 하지만 이 두 함수는 내부적으로 다른 방법을 사용해 값을 찾는다.
max
함수
max
함수는s[0]
을 초기max
값으로 설정한다.1..s.len()
범위로 for 루프를 돌며, 배열의 각 원소(s[i]
)와 현재max
값을 비교한다.- 만약 배열의 원소가
max
보다 크다면,max
값을 그 원소로 업데이트한다. - 루프가 끝나면 가장 큰 값을 반환한다.
max2
함수
max2
함수는 역시s[0]
을 초기max
값으로 설정한다.- 이 함수는 배열의 각 원소에 대해
for x in s
루프를 돌린다. 여기서x
는&i32
타입이다. *x > max
비교를 통해 원소 값과max
값을 비교한다.max
가 원소보다 작다면,max
값을 그 원소로 업데이트한다.- 루프가 끝나면 가장 큰 값을 반환한다.
차이점
max
함수는 인덱스를 사용해 배열을 순회한다.max2
함수는for x in s
구문을 사용해 배열을 순회한다. 이 방법은 좀 더 "Rust스럽다"고 할 수 있다. <U>여기서x
는 참조(&i32
)이기 때문에, 실제 값을 사용하기 위해*x
로 역참조한다.</U>
두 함수는 동일한 작업을 수행하지만, max2
함수가 좀 더 Rust의 특성에 맞는 방식으로 배열을 순회한다.
fn find_even(s: &[i32]) -> Option<i32> {
for x in s {
if *x % 2 == 0 {
return Some(*x);
}
}
None
}
find_even
함수는 정수의 배열 또는 벡터의 참조(&[i32]
)를 입력으로 받아서 그 중에 짝수가 있으면 그 짝수를 반환한다. 반환 타입은 Option<i32>
인데, 짝수를 찾으면 Some(짝수)
를 반환하고, 없으면 None
을 반환한다.
- 함수는
for x in s
루프를 돌면서 배열의 각 원소(x
)를 확인한다. 이때x
는&i32
타입이다. if *x % 2 == 0
조건문을 통해 해당 원소가 짝수인지 확인한다. 짝수면Some(*x)
를 반환한다. 여기서*x
는 역참조를 통해 원래 값(i32
)을 가져온다.- 배열에 짝수가 없다면 루프가 끝날 때까지
Some(*x)
를 반환하지 않고, 마지막에None
을 반환한다.
간단하게 요약하면, 이 함수는 주어진 배열 또는 벡터에서 첫 번째로 발견되는 짝수를 반환하거나, 짝수가 없으면 None
을 반환한다.
댓글 없음:
댓글 쓰기