타나기
타나기 월드
타나기
전체 방문자
오늘
어제
  • 분류 전체보기 (90)
    • ⚙️ Rust (20)
      • 👀 Tutorial (20)
    • 🗿 Embedded (11)
      • 🐧 OS for ARM (11)
    • 💻 Study (37)
      • 🧩 알고리즘 (37)
    • 🏄🏽‍♂️ Life (21)
      • 🍚 타나구루망 (20)
      • 💡 Light (1)

인기 글

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
타나기

타나기 월드

⚙️ Rust/👀 Tutorial

[Rust / 튜토리얼] 10. 러스트의 슬라이스 (slice)

2022. 5. 25. 22:11

슬라이스

소유권을 갖지 않는 또 다른 데이터 타입은 슬라이스가 있다.
슬라이스는 컬렉션의 전체가 아닌 요소들에 접근이 가능 하다.
스트링을 입력 받아 첫번째 스트링을 리턴하는 함수를 예시로 삼아 배워 보자.
우선은 String에서 첫번째 단어의 길이를 가져와 보자.


fn get_first_word(sentence: &String) -> usize {
    let bytes = sentence.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return i;
        }
    }
    sentence.len()
}

fn main() {
    let sentence = String::from("Hello World!");
    let length = get_first_word(&sentence);
    println!("len : {}", length);
}
   Compiling test_exam v0.1.0 (C:\Folder\source\rust\test_exam)
    Finished dev [unoptimized + debuginfo] target(s) in 0.98s
     Running `target\debug\test_exam.exe`
len : 5

let bytes = sentence.as_bytes();

  • String 을 바이트 배열로 변환

bytes.iter().enumerate()

  • 바이트 배열의 각 요소들을 튜플의 일부로 만들어 리턴
  • iter와 함께 사용되어 배열의 요소들을 탐색함.
  • 자세한건 나중에 배워보자

if item == b' '

  • 요소가 공백이라면 i를 리턴하는데 i는 요소를 탐색할 때마다 1씩 증가하므로 첫번째 단어의 길이를 리턴한다.

위의 코드로 우리는 위의 첫번째 단어의 길이를 알게 되었다.
그런데 리턴받은 인덱스 값으로 스트링 슬라이스 하기엔 버그가 발생할 수 있다.
함수에 인자로 전달해 준 sentence 변수가 함수가 실행된 뒤 변경될 수 있기 때문이다. 예를들어,

fn main() {
    let mut sentence: String = String::from("Hello World!");
    let length = get_first_word(&sentence);
    sentence = String::from("make some bug");
    println!("len : {}", length);
}

이렇게 극단적인 예시를 들면, 버그가 발생할 수 있다.
sentence가 완전히 변경되어 길이가 더 이상 5가 아니기 때문이다.
러스트에서는 스트링 슬라이스를 이용해 이런 문제를 해결할 수 있다.

스트링 슬라이스

스트링 슬라이스는 String의 일부분에 대한 참조자이고, 아래처럼 사용 가능하다.

fn main() {
    let sentence = String::from("hello world");

    let hello = &sentence[0..5];
    let world = &sentence[6..11];

    println!("{} {}", hello, world);
}
   Compiling test_exam v0.1.0 (C:\Folder\source\rust\test_exam)
warning: `test_exam` (bin "test_exam") generated 1 warning
    Finished dev [unoptimized + debuginfo] target(s) in 0.88s
     Running `target\debug\test_exam.exe`
hello world

코드를 보면 대괄호 [] 를 이용해 슬라이싱을 한다!
매우 쉽고 간편해서 파이써닉한 느낌마저 준다.
start..end 문법은 start 값은 포함하고, end 값은 포함하지 않는다.
그래서 0번째 인덱스부터 4번째 인덱스까지 포함하는 String을 슬라이싱 하고 싶다면 4에서 1을 더한 5를 end 값으로 적어 주어야 한다.
이걸 표현해 보면 [0..5] 가되겠다.

러스트의 쉽고 간편한 .. 문법을 사용할 때 몇가지 꿀팁이 있다.

  1. 첫번 째 인덱스는 생략 가능하다.
  • 아래 두개의 slice는 동일하다.
    let sentence = String::from("hello");
    let slice = &sentence[0..2]
    let slice = &sentence[..2]
  1. 끝의 인덱스도 생략 가능하다.
  • 아래 두개의 slice는 동일하다.
    let sentence = String::from("hello");
    let len = sentence.len()
    let slice = &sentence[3..len]
    let slice = &sentence[3..]
  1. String 전체 슬라이스를 만들기 위해 양쪽 값 모두 생략 가능하다.
  • 아래 두개의 slice는 동일하다.
    let sentence = String::from("hello");
    let len = sentence.len()
    let slice = &sentence[0..len]
    let slice = &sentence[..]

여기까지의 내용을 바탕으로 제일 처음의 함수를 완성 해 보자

fn get_first_word(sentence: &String) -> &str {
    let bytes = sentence.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &sentence[..i]
        }
    }
    &sentence[..]
}

fn main() {
    let sentence = String::from("hello world");

    let first = get_first_word(&sentence);
    println!("first : {}", first);
}
   Compiling test_exam v0.1.0 (C:\Folder\source\rust\test_exam)
    Finished dev [unoptimized + debuginfo] target(s) in 0.88s
     Running `target\debug\test_exam.exe`
first : hello

제일 처음에 작성했던 공백까지의 길이를 가져오는 함수를 응용했다.
공백은 단어에 포함되지 않으니까 [0..i] 로 표현하면 첫 번째 단어를 가져올 수 있다.
여기서 약간 주의해야 할 점은, 참조자 & 를 야무지게 붙여줘야 한다는 점이다.
빌려주는 참조자와 슬라이싱하는 참조자는 참 헷갈린다. (&String <-> &str)
이럴 때 &str은 []를 이용해 &String의 일부분 만 빌리는 참조자임을 계속 떠올리면 확실하게 구별할 수 있을 것이다.

자 여기까지 작성한 코드는 이전 코드보단 버그를 발생시키기 어려워 졌다.
그럼 아까 버그가 발생했던 코드를 돌려보자.

fn main() {
    let mut sentence: String = String::from("Hello World!");
    let first = get_first_word(&sentence);
    sentence = String::from("make some bug");
    println!("len : {}", first);
}
   Compiling test_exam v0.1.0 (C:\Folder\source\rust\test_exam)
warning: value assigned to `sentence` is never read
  --> src\main.rs:16:5
   |
16 |     sentence = String::from("make some bug");
   |     ^^^^^^^^
   |
   = note: `#[warn(unused_assignments)]` on by default
   = help: maybe it is overwritten before being read?

error[E0506]: cannot assign to `sentence` because it is borrowed
  --> src\main.rs:16:5
   |
15 |     let first = get_first_word(&sentence);
   |                                --------- borrow of `sentence` occurs here
16 |     sentence = String::from("make some bug");
   |     ^^^^^^^^ assignment to borrowed `sentence` occurs here
17 |     println!("len : {}", first);
   |                          ----- borrow later used here

For more information about this error, try `rustc --explain E0506`.

보는 것 처럼, sentence는 빌려준 값이기 때문에 컴파일 오류가 발생한다.
위의 오류는 불변 참조자인 &str을 지우려고 시도했기 때문에 발생했다.
이처럼 불변 참조자 인 &str은 여러 버그를 사전에 방지해 준다.

스트링 슬라이스를 이용한 개선

위의 함수를 한번 더 개선할 수 있다.
fn get_first_word(sentence: &String) -> &str { 부분을
fn get_first_word(sentence: &str) -> &str { 로 바꾸면 된다.
&String 을 &str로 변경하면 두가지 타입의 변수 모두 함수의 인자로 받을 수 있다는 장점이 있다.
&String은 &str로 쉽게 변환이 되지만 그 반대는 어렵기 때문인데, 아래의 코드를 통해 살펴 보자.

코드는 튜토리얼 예제에서 긁어왔다.

fn main() {
    let my_string = String::from("hello world");

    // first_word가 `String`의 슬라이스로 동작
    let word = get_first_word(&my_string[..]);
    println!("word : {}", word);

    let my_string_literal = "hello world";

    // first_word가 스트링 리터럴의 슬라이스로 동작
    let word = get_first_word(&my_string_literal[..]);
    println!("word : {}", word);

    // 스트링 리터럴은 (""로 쌓인 문자열) 스트링 슬라이스이기 때문에,
    // 아래 코드도 슬라이스 문법 없이 동작한다.
    let word = get_first_word(my_string_literal);
    println!("word : {}", word);
}
   Compiling test_exam v0.1.0 (C:\Folder\source\rust\test_exam)
    Finished dev [unoptimized + debuginfo] target(s) in 0.87s
     Running `target\debug\test_exam.exe`
word : hello
word : hello
word : hello

그 밖의 슬라이스

스트링 말고도 슬라이스가 존재한다.
우리가 다른 언어에서 익숙하게 사용했던 배열들이다.

let a = [1, 2, 3, 4, 5]; 
let slice = &a[1..3];

사용법은 스트링 슬라이스와 다르지 않다.
참조자를 붙여주고, [] 를 사용해 슬라이싱 하면 된다.
앞의 예제와 똑같이, 슬라이스 하게 되면 불변으로 변하게 됨만 잘 기억하면 된다.
이런 슬라이싱은 다른 모든 종류의 컬렉션에 대해 사용할 수 있다.

정리

와 멀고도 험한 소유권, 빌림, 그리고 슬라이스까지 공부했다.
러스트 기초의 20프로정도 공부한 것 같다.
여기까지 배운걸 보면 러스트는 프로그램의 메모리 안정성을 컴파일 타임에 최대한 보장함을 알 수 있다.
C/C++ 같은 언어들은 메모리 사용에 대해 최대한의 자유도를 주고 그에 따른 책임도 지지만, 러스트는 메모리의 사용을 제한한 대신 프로그램의 안정성을 보장해 주는 느낌이다.

소유권에 대한 개념은 러스트의 기본으로, 다른 부분들이 어떻게 동작하는 지에 대해 이해하는데 필요한 부분이다.
이제 남은 부분은 지금까지 배운 내용을 바탕으로 더욱 심화된 내용일 것이다.

우선 다음으로 struct에 대해 알아보자

저작자표시 (새창열림)

'⚙️ Rust > 👀 Tutorial' 카테고리의 다른 글

[Rust / 튜토리얼] 12. 러스트 구조체를 이용한 예제 프로그래밍  (0) 2022.05.29
[Rust / 튜토리얼] 11. 러스트에서 구조체를 정의하고 생성하기  (0) 2022.05.27
[Rust / 튜토리얼] 9. 러스트의 참조자와 빌림  (0) 2022.05.23
[Rust / 튜토리얼] 8. 러스트의 소유권  (0) 2022.05.20
[Rust / 튜토리얼] 7. Rust의 제어문  (0) 2022.05.16
    '⚙️ Rust/👀 Tutorial' 카테고리의 다른 글
    • [Rust / 튜토리얼] 12. 러스트 구조체를 이용한 예제 프로그래밍
    • [Rust / 튜토리얼] 11. 러스트에서 구조체를 정의하고 생성하기
    • [Rust / 튜토리얼] 9. 러스트의 참조자와 빌림
    • [Rust / 튜토리얼] 8. 러스트의 소유권
    타나기
    타나기
    #include<all>

    티스토리툴바