[Rust] String

김동현·2023년 10월 28일

Rust

목록 보기
1/2
post-thumbnail

String

  • String 타입을 선언하게 되면 선언된 변수는 스택에 할당 된다.
  • String 타입으로 선언된 문자열 객체는 에 할당 된다.
  • 코드로 한번 찍어보자
fn main () {
  println!("{}", '\n');
  // 1. String 타입의 문자열 객체 선언
  let string_타입 = String::from("힙"); 

  // 2. 선언된 String 타입의 주소를 String_타입_주소에 복사합니다.
  let string_타입_주소 = &string_타입; 

  // 3. 변수의 주소를 출력합니다.

  // 3-1. 힙에 있는 실제 객체의 주소
  println!("힙에 있는 실제 객체의 주소 {:p} \n", string_타입.as_ptr()); 
  // 3-2. 스택에 있는 변수의 실제 주소
  println!("스택에 있는 변수의 실제 주소 {:p} \n", string_타입_주소);

  // 4. String 타입의 소유권을 이동시킵니다.
  let string_타입_새로운_소유자 = string_타입;
  // 5. 이동시킨 String 타입의 주소를 복사합니다.
  let string_타입_새로운_소유자_주소 = &string_타입_새로운_소유자;

  // 6. 변수의 주소를 출력합니다.
  // 6-1. 힙에 있는 실제 객체의 주소
  println!("힙에 있는 실제 객체의 주소 {:p} \n", string_타입_새로운_소유자.as_ptr());

  // 6-2. 새로 생성된 소유자의 스택 주소
  println!("스택에 있는 변수의 실제 주소 {:p} \n", string_타입_새로운_소유자_주소);
  
  println!("{}", '\n');
}

String 타입의 특징

  • String은 사실 Vec 형태의 구조체이다.
    • 즉, 동적으로 변경 가능하다!
pub struct String {
    vec: Vec<u8>,
}

String 타입 선언 방법

  let 스트링_타입 = String::new();
  let 이것도_스트링_타입 = "스트링타입".to_string();

  let 난_str_타입 = "data";
  let 이렇게하면_스트링_타입 = 난_str_타입.to_string();

  let 바로_값_넣고_할당 = String::from("스트링 타입이에요");

String에 값 붙이기

	let mut string = String::new();
    // char 타입은 따옴표로 감싸야 한다.
  	string.push('일');
    // str타입은 쌍따옴표로 감싸야한다.
  	string.push_str("하나");
	dbg!(string);
  1. push
  • push는 문자열 하나 즉, char 타입을 매개변수로 받는다.
  • char 타입은 따옴표로 감싸야 한다.
  1. push_str
  • push_str은 &str 타입을 매개 변수로 받는다.
  • str 타입은 쌍따옴표로 감싸야한다.

String 타입의 더하기

  let str1 = String::from("Hello, ");
  let str2 = String::from("world!");
  let str3 = str1 + &str2;

  dbg!(str1); // use of moved value: `s1` value used hear after move
  dbg!(str2);
  dbg!(str3);
```rust
let str1 = String::from("Hello, ");
let str2 = String::from("world!");
let str3 = str1 + str2; // expected &str, found String

dbg!(str1);
dbg!(str2);
dbg!(str3);
  • 아래의 사진을 보면 + operator는 Add 메소드를 사용한다.
  • str1 + &str2를 하게되면 add(str1, str2)를 하게 되는 것과 유사한데 첫 str1은 self로 구조체 자기 자신이 된다.
    • 이는, str1은 자기 자신이며, str1의 소유권을 가지고 str2의 내용을 참조하여 더한 뒤, 소유권을 반환하는 것이다!!

두 번째 코드 스니펫을 보게되면 &str 값이 들어와야 된다고 하는데 String이 들어와 있어 컴라일러가 에러를 뱉어낸다.

  • 🤔 하지만, &str2는 &str 타입이 아니라 &String 타입이다. 어떻게 &String 타입을 넣을 수 있을까?

  • ☑ Rust는 &String을 &str로 강제한다!
    Rust에서 deref coercion(역참조 강제)는 메서드나 함수 호출 시 자료형을 자동으로 역참조하는 언어 기능이다! << 조금더 공부가 필요하겠다 ㅠㅠ

결국 String 타입의 덧셈은 더하기 연산자의 첫 번째 매개 변수의 소유권을 가지고 두 번째 매개 변수의 값을 참조하여 더한 뒤 소유권을 반환하는 과정이다!

format! 매크로
😎 format 매크로는 소유권을 가지지 않고 더해주는 멋진 녀석이다!

	let string1 = String::from("김");
    let string2 = String::from("동");
    let string3 = String::from("현");
    
    let string_add = format!("{}{}{}", string1, string2 ,string3);

    dbg!(string1);
    dbg!(string2);
    dbg!(string3);
    dbg!(string_add);

String 인덱싱

let string1 = String::from("hello");
let idx = string1[0];
//the type `String` cannot be indexed by `{integer}` the trait `Index<{integer}>` is not implemented for `String` the following other types implement trait `Index<Idx>`:

러스트의 String 타입은 인덱싱을 지원하지 않는다!!

위에 이야기 한 것 처럼 String은 문자열이 아니라 Vec의 Wrapper이다.
또한, Rust의 String은 UTF-8을 기준으로 문자열을 관리한다.
🤔 UTF-8은 문자열마다 가질 수 있는 바이트의 길이가 최소 1바이트에서 최대 4바이트로 고정된 바이트 개수를 가지지 않는다!!
-> 그렇기에 인덱싱으로 전달되는 숫자가 언제나 새로운 단어의 시작이라는 점을 항상 만족할 수가 없다!

let string1 = String::from("abcde");
dbg!(string1.len()); // 5

let string2 = String::from("ㄱㄴㄷㄹㅁ");
dbg!(string2.len()); // 15
// string2의 7번째를 찍게 되면 완성되지 않은 ㄷ의 중간쯤이지 않을까?

문자열 슬라이싱

fn main () {
  let string2 = String::from("ㄱㄴ");
  dbg!(&string2); // 15

  for c in string2.chars() {
    println!("{}", c);
  }
  for c in string2.bytes() {
    println!("{}", c);
  }
  // 227
  // 132
  // 177
  // 227
  // 132
  // 180

  dbg!(&string2[0..3]); // ㄱ
  dbg!(&string2[0..4]); // it is inside 'ㄴ' (bytes 3..6) of `ㄱㄴ`'
}
  • 문자열 슬라이싱은 1바이트를 기준으로 슬라이싱을 한다.
    -> 0..4를 하게되면 4바이트를 자른다는 것인데 UTF-8에서 ㄱ과 ㄴ은 각각 3바이트를 가지게 되므로 0..4는 ㄴ의 중간 어디쯤까지 문자열 슬라이싱을 하겠다는 게 된다!!

즉, 문자열 슬라이싱은 어렵다..
영어, 한국어, 아랍어 등등 가지고 있는 문자열의 바이트 개수들이 다르기 때문이다!!
😂 러스트의 String은... 주의 해서 쓰도록 하자


요약!

  1. 러스트의 String 타입은 Vec<u8>의 Wrapper 타입이다.

  2. 러스트의 String 타입은 소유권을 가지며 어느 한 시점에 반드시 하나의 소유자가 존재한다.

  3. String의 변수는 Stack 영영에 존재하고, 실제 값은 Heap 영역에 존재한다.

  4. Rust의 문자열은 UTF-8을 기준으로 하기에 슬라이싱 하는 데 어려움이 있다.

    • 영어는 1바이트, 한국어는 3바이트이다!
  5. String 타입을 소유권을 가지는 함수가 사용하고 난 뒤에는 사라지게 된다!

  6. String의 더하기 연산자(+)는 첫 번째 피 연산자의 소유권을 가지고 두 번재 피연산자의 값을 참조하여 더한 뒤 뱉어내는 메서드이다.


    참고 자료

profile
달려보자

0개의 댓글