스트링(문자열)
러스트의 스트링은 좀 복잡함. 스트링이 컬렉션에 포함된 이유는, 스트링은 텍스트로 표현되는 바이트의 집합 +이 바이트를 다루는 유용한 메서드들로 구현되어 있음. 이런 성질은 본질적으로 컬렉션 타입의 것이라 볼 수 있음.
스트링이란?
러스트에는 크게 두 가지 스트링 타입이 존재함: str 과 String.
str:
러스트 코어 존재하는 유일한 스트링 타입임. 레퍼런스 대여 코드에 더 많이 보이기 때문에 보통 &str 형태로 보임. 스트링 슬라이스의 타입이기도 한 이 타입은 그 위치가 어디이든 UTF-8 로 인코딩된 모든 문자열을 참조함. 따라서, 프로그램의 바이너리에 위치하는 스트링 리터럴도 스트링 슬라이스라 할 수 있음.
String:
std 라이브러리에 의해 제공되는 스트링 타입임. 이 타입은 늘어날 수 있고(growable), 뮤터블이며, 소유할 수 있고, UTF-8 임.
러스트 프로그래머가 '문자열(스트링)' 을 이야기할 때, 이는 대부분 &str 과 String 을 동시에 의미함. (둘 중 하나만 떼어놓고 이야기하지 않는다는 의미.) 두 스트링 타입 모두 러스트에서 중요하게 다루는 타입이며, 모두 UTF-8 인코딩을 취한다는 사실을 기억해야 함.
std 는 또한 OsString, OsStr, CString, CStr 과 같은 다른 스트링 타입도 제공함. 이들의 이름은 String/&str 처럼 *String/*str 의 형태를 가짐. 이런 타입들은 다른 인코딩 타입이거나 하는 등 각각의 특징을 가지고 있음. 여기서는 자세히 다루지 않으니, 궁금한 사항은 API 를 보길 바람.
새 스트링 생성
아래는 String 타입의 빈 스트링을 생성함.
아래는 초기값을 가진 String 타입의 스트링을 생성함.
아래는 초기값을 가진 또 다른 방법. 위의 방법과 결과적으로 똑같다.
스트링 값 변경
문자열을 더하는 방법에는 메서드 push_str 과 push 이 있음.
push_str 메서드는 파라미터로 스트링 슬라이스를 받음. 때문에 파라미터 원본의 오너십을 가지지 않음.
push 메서드는 문자 하나를 파라미터로 받음.
format! 매크로와 '+' 연산자를 이용한 문자열 이어붙이기
다른 언어에서 흔히 그렇듯, 러스트도 '+' 연산자를 이용한 문자열 붙이기가 가능함.
s3 는 'Hello, world!' 가 됨. 그런데 주석에서 보다시피, s1 은 이제 버려지고, s2 은 여전히 유효함. 그 이유는, 여기 '+' 연산자는 add 메서드를 사용함. 아래는 그 시그니처.
이건 사실 std 라이브러리의 정확한 시그니처는 아님. 정확히는 제너릭이 사용되어 있음. 일단 여기서는 중요하지 않으니 패스.
여기서 눈여겨볼 점은 따로 있음. 이 메서드는 두 번째 파라미터로 &str 을 받아서 첫 번째 파라미터인 자기 자신, 즉 String 에 더함. '+' 연산자를 사용할 때 우린 &str 을 String 에 더하는 것이며, String 과 String 을 합칠 수는 없음. 그런데, 위의 예제에서 s2 은 String 임. 그리고 &s2 는 &String 이지, &str 이 아님. 그런데 add 메서드의 파라미터로 쓰일 수 있는 이유는?
컴파일러는 인자값으로 넘어온 &String 를 &str 으로 강제함. add 메서드를 호출하면 러스트는 deref coercion 을 사용하는데, 이는 &s2 를 &s2[...] 로 바꿈. deref coercion 에 대해서는 나중에 자세히 다루니 여기선 패스. 아무튼 그렇고, add 메서드는 파라미터 s 의 오너십을 취하지 않음. 때문에 변수 s2 는 add 후에도 여전히 유효함.
그리고, add 의 시그니처에서 self 는 오너십을 취함. let s3 = s1 + &s2; 은 얼핏 보기에 두 스트링을 복사하여 하나의 새로운 스트링을 생성하는 것처럼 보이지만, 사실은 s1 의 오너십을 취한 뒤 여기에 s2 의 내용을 복사하여 더하고(append), 오너십을 다시 리턴하는 것임. 다시 말해서, 보기와 달리 이 과정에서 많은 복사 작업이 발생하지 않음. add 의 구현 방식은 단순 복사보다 효율적으로 이루어져 있음.
더 많은 수의 스트링을 더할 수도 있음.
이런 작업이 복잡해질 경우, format! 매크로를 사용할 수도 있음.
위 두 예제에서 s 는 같은 값을 가짐. 그런데 format! 매크로는 오너십을 취하지 않기 때문에, s1, s2, s3 모두 유효함.
스트링 인덱싱
위 코드는 컴파일 에러를 일으킴. 즉 러스트 스트링은 인덱싱을 지원하지 않음. 이유를 알아보자면,
내부적으로 String 은 Vec<u8> 의 래퍼(wrapper)임.
위 코드에서 len 은 4 가 됨. 이는 "Hola" 를 저장하고 있는 Vec 의 길이가 4바이트라는 의미임. UTF-8 에서 각 문자는 1바이트씩 차지함. 그렇다면 다음 코드는?
위 문자열의 첫 문자는 키릴 문자의 대문자 Ze 임. 숫자 3 이 아님. len 의 값은 12가 아닌 24인데, 이는 위 문자열이 UTF-8 에서 24바이트를 취했다는 의미임. 각 유니코드 스칼라 값이 2바이트씩 차지함. 이렇게 러스트 스트링의 인코딩인 UTF-8 에서 스트링의 인덱스가 언제나 하나의 문자의 위치라는 것을 보장할 수 없음.
answer 의 값은 무엇이 되어야 함? 첫 번째 문자? UTF-8 에서 첫 번째 문자는 208 바이트임. 두 번째는 151임. 그리고 이미 알아봤듯 이 문자열은 문자 하나가 2바이트를 차지함. 때문에 첫 번째 바이트인 208이나, 두 번째 바이트인 151이 각자 단독으로는 유효한 문자가 되지 못함. 때문에 인덱스 0 으로 이 문자를 리턴할 수가 없음. 그래도 어쨌든 첫 번째 인덱스이니 일단 208 을 리턴하도록 한다면, 유저는 첫 번째 인덱싱으로 나온 바이트이니 이 값이 첫 번째 문자의 바이트라고 잘못 인식하게 될 여지가 있고, 이는 유저가 원하는 데이터가 아님. 이런 문제로 인해 러스트는 이런 방식의 스트링 인덱싱 접근을 허용하지 않음.
바이트와 스칼라 값 그리고 문자 집합
데바나가리 스크립트로 쓰려진 힌디 단어 'नमस्ते' 은 Vec 의 u8 값으로 다음과 같이 저장됨.
[224, 164, 168, 224, 164, 174, 224, 164, 184, 224, 165, 141, 224, 164, 164, 224, 165, 135]
이건 18바이트이며 컴퓨터가 최종적으로 데이터를 저장하는 방식임. 이걸 유니코드 스칼라 값으로 본다면 다음과 같음. 이는 러스트에서 char 타입 값임.
['न', 'म', 'स', '्', 'त', 'े']
이렇게 여섯 개의 char 값이 됨. 그런데 4, 6번째 값은 문자가 아님: 이건 diacritic 이라는, 스스로 문자화 될 수 없는 값임. 결국 이 값들을 문자 집합으로 본다면, 사람이 보기에 다음 4개의 문자가 하나의 단어로 되어버림. 4, 6번째 값은 버려지고, 남은 값들로 단어가 구성되어 원래 단어와 값이 달라짐.
["न", "म", "स्", "ते"]
러스트는 프로그램이 어떤 언어의 문자든 적절하게 해석할 수 있도록, 컴퓨터가 저장하는 원천 스트링 데이터를 해석하는 여러가지 방법을 제공함.
러스트가 String 에 인덱싱 접근을 허용하지 않는 마지막 이유는, 인덱싱 연산은 언제나 정적 시간(O1(1)) 을 취해야 한다고 여겨지기 때문임. 러스트는 문자열 안의 유효한 문자의 수를 도출하기 위해 문자열의 처음부터 끝까지 순회해야 하기 때문에 String 으로 정적 시간 연산 성능을 보장할 수 없음.
스트링 슬라이싱
문자열 인덱싱 접근은 별로 좋은 방법이 아님. 왜냐하면 리턴 타입이 뭐가 되어야 할지 분명하지 않기 때문. 바이트인지, 문자인지, 문자열인지... 때문에 러스트는 이에 대하여 범위를 지정한 슬라이스를 사용하도록 권함.
s 는 &str 타입이 되고, hello 의 처음 4바이트를 참조함. 위 문자가 각 2바이트를 취한다고 했으니, s 는 처음 두 문자를 참조하게 됨.
&hello[0..1] 를 참조한다면? 이건 panic 이 발생함. 이건 무효한 인덱싱 접근과 마찬가지임. 이런 결과가 발생할 수 있으므로 스트링 슬라이싱을 사용함에 주의가 필요함.
문자열 순회 메서드
아래 방법으로 문자열의 각 문자를 순회할 수 있음. char 메서드는 문자열의 각 문자를 나누어줌.
아래는 바이트. bytes 메서드는 문자열의 각 문자를 바이트로 나누어줌.
아무튼 스트링은 복잡함.
'Rust' 카테고리의 다른 글
에러 핸들링(panic) (0) | 2018.02.26 |
---|---|
컬렉션(해시맵) (0) | 2018.02.25 |
컬렉션(벡터) (0) | 2018.02.24 |
모듈(다른 모듈 접근) (0) | 2018.02.22 |
모듈(pub) (0) | 2018.02.22 |