시뻘건 개발 도전기

러스트 12 - 슬라이스 타입 본문

프로그래밍/RUST

러스트 12 - 슬라이스 타입

시뻘건볼때기 2019. 3. 3. 18:22
반응형




소유권을 갖지 않는 테이터 타입은 또 있다. 바로 "Slice"

슬라이스를 사용하게 된다면 Collection의 전체가 아닌 연속된 요소(Element) Sequence를 참조 할 수 있게 된다.


String에서 첫 단어를 찾는 코드를 보자.

fn main() {

    let string_slice = String::from("String Slice");

    let idx = get_index(&string_slice);


    println!("idx : {}", idx);

}


fn get_index(str: &String) -> usize {    // 소유권 X

    let str_bytes = str.as_bytes();         // 들어온 str의 바이트를 겟!


    // enumerate : iterator의 결과 값을 직접 반환 (튜플로)

    // i : index

    // item : element에 대한 참조 값

    for (i, &item) in str_bytes.iter().enumerate() {

        if item == b' ' {    // 바이트 리터럴 구문을 사용하여 공백을 나타내는 바이트 검색

            return i;          // 공백이면 해당 index 반환

        }

    }

    str.len()                  // 공백이 없으면 str 길이를 반환

}


"String"이라는 단어와 "Slice"라는 단어를 이어서 만든 문자열에서 첫 단어 "String"만을 알고 싶을 때, 공백의 index를 반환해주는 함수가 있다.

만일, index를 무리 없이 잘 반환이 되었다고 치자. 그 뒤에 string_slice의 값이 변경이 되었는지, 초기화 되었는지 어떻게 보장할 것인가?

코드가 서너줄이라면 뭐 그냥저냥 쓰겠지만.. 덩치가 크면 클 수록 보장을 해주어야 한다.(주석보다 확실하게...?)

index 관련 오류는 쉽게 찾을 수 있지 않다... 물론 내가 경험이 많지는 않지만 어디서 index exception이 났는지는 바로 인지가 가능하지만, 도대체 어떻게 index가 비워졌는지 혹은 추가 되었는지, 왜 이렇게 되었는지 코드를 보는데 시간이 꽤 걸린다...

이러한 문제를 해결해줄 녀석이 "스트링 슬라이스(String Slice)"라고 한다.



스트링 슬라이스는 String의 일부에 대한 참조자라고 할 수 있다.

fn main() {

    let string_slice = String::from("String Slice");

    let str = &string_slice[0..6];

    let slice = &string_slice[7..12];


    println!("first word : [{}]", str);

    println!("second word : [{}]", slice);

}


"&string_slice[0..6]" 부분을 해석해보면 다음과 같다.

"string_slice의 0부터 (6-1)까지(일부만) 참조 하겠다."라고 볼 수 있다.

rust docs에 보면 [start..end] -> start부터 end를 포함하지 않는 연속된 범위러고 정의되어 있으니...같은 말이지요.

"0부터 x까지 참조하겠다!"라고 한다면 0은 생략 가능하다. ( [..x] ) : 반대로 [x,,]은 끝에 마지막 바이트까지 포함.

처음과 끝의 모든 바이트를 참조하겠다라면 [..]도 가능하다. (사용하진 않을 것같은 냄새가 난다..ㅋㅋㅋㅋㅋ)


그럼 이번 포스팅에서 배운 스트링 슬라이스를 상요해서 반환하는 함수를 만들어보자.


fn main() {

    let string_slice = String::from("String Slice");

    let word = get_word(&string_slice);


    println!("first word : {}", word);

}


fn get_word(str: &String) -> &str {    // 문자열 반환

    let str_bytes = str.as_bytes();        // str 바이트 겟.


    for (i, &item) in str_bytes.iter().enumerate() {

        if item == b' ' {                    // 바이트 구분

            return &str[0..i];

        }

    }

    &str[..]

}


문자열을 반환하는 함수 get_word는 공백을 발견하면 그 즉시 처음부터 공백까지 스트링 슬라이스로 부분 참조를 시도하여 반환한다.







추가로 한가지 더 정의된 내용이 있다.


string 리터럴은 "슬라이스"다.

string 리터럴의 경우, binary 내부에 저장된다.


let string_slice = "string slice";

// string_slice 의 타입은 "&str"이다. 이것은 binary의 특정 포인터를 가르키는 슬라이스라는 것.

// string 리터럴이 불변인 이유라는 것.


...


fn get_word(str: &str) -> &str {}

// String과 &str 둘 모두에 대 한 같은 함수를 사용한다는 것. (같은 녀석이고 다르게 쓴다고 이해하면 될 듯...)




스트링 슬라이스에 대한 내용이 상당히 심오하다... 처음 보는 녀석이라 그런지 소유권부터 시작해서 심상치가 않다..

소유권 / borrowing / slice의 개념은 compile time에 메모리를 보다 더 안전하게 사용할 수 있는 녀석들이다.

rust는 다른 시스템 프로그래밍 언어와 같은 방식으로 메모리 사용을 제어한다. 그러나 소유권에 의해 자신의 scope를 벗어나면 메모리를 자동으로 반환하여 따로 디버깅이 필요가 없어진다. 단... 이러한 것들을 완벽하게 이해하고 자유롭게 사용할 수 있다면 어할 나위 없이 좋고...ㅎㅎㅎㅎ


앞으로 뭔가 험난한 길이 예상된다 ㅋㅋㅋㅋㅋㅋㅋㅋㅋ

반응형
Comments