파이썬에서
와 같은 코드를 러스트에서는 다음과 같이 쓴다.
근데 숙련된 러스트 사용자라면 위 코드에서 에러가 나는 것을 안다. 왜냐하면 우변의 x[0] + &x[1] 에서 +연산자는 왼쪽 항(left hand side)인 x[0] 의 소유권을 consume하지만 배열은 partially move 될 수 없어 자신이 소유한 원소의 소유권을 넘길 수 없다. 그렇다면 다음처럼 AddAssign을 이용하면 어떨까?
이것 역시 에러가 난다. 보통 =인 대입은 move 연산으로 어떠한 함수도 호출하지 않지만 +=인 AddAssign은 함수이기 때문에 x[0] += &x[1]; 는 std::ops::AddAssign::add_assign(&mut x[0], &x[1]); 와 동일한 코드이다. 여기서 함수인자로 &mut x[0] 와 &x[1]를 동시에 받아 x에 대한 mutable reference와 immutable reference를 같이 취하고 있어 borrow check에서 에러가 난다. 해결법은 무엇일까? 문제점의 시작은 x[0]와 x[1]가 다른 값을 가리킴에도 불구하고 두 값의 reference를 x로부터 빌렸다고 보기 때문에 그렇다. 변수 색인(index)의 경우 컴파일 시간에 둘이 같은지 다른지 확인할 수 없으므로 두 참조자는 x를 빌렸다고 보는 것이다. 이런 문제를 해결하는 방법은 slice를 split_at을 이용해서 나누는 것이다. split_at은 슬라이스를 2개의 슬라이스로 나누어준다.
원하는 결과가 잘 나온다. 다른 방법은 없을까? 간단한 방법이 있다
x[0] = std::mem::take(&mut x[0]) + &x[1];
위처럼 약간의 런타임 오버헤드를 감수하면 편리함이 온다. 앞으로 러스트를 하면서 이런 성능과 편리함의 유혹이 많을텐데 러스트에서는 일상이다. 둘 중 어느것을 취하든 나쁘다고 할 수 없다. 간단함과 성능을 모두 챙기고 싶다면 다음과 같이 사용하면 된다.
이 방법을 나는 참조자 세탁(reference laundering)이라고 부른다. 한 번만 포인터가 되었다가 reference로 돌아가면 borrow check를 피해갈 수 있다. 또는 std::mem::transmute::<&str, &str>() 로 참조자를 세탁해도 된다. 프로그래머가 안전하다고 확신이 들면 unsafe를 사용하며 주석으로 안전한 이유를 설명하자.
확인하기 문제: 1. 튜플이나 구조체의 경우에는 위 같은 문제가 발생하는가? 2. split_at은 런타임 오버헤드가 있는가?
이거 보고 꽃게북 찢었다
러스트가 편집증적 튜닝과 귀차니즘 코딩 둘 다 할 수 있는 거 같음
reference laundering 이게 실제 용어임? 저렇게 쓰이는 거 몇 번 봤는데 뭐라 찾아야할지 몰랐는데 진짜 저거 보고 얘도 C++ 못지않게 hack 스런거 많다 싶었음
내가 지어냄
저거 안티패턴임
꼭 안써도 되는데 unsafe떡칠
https://godbolt.org/z/cTYM8vf8T
clone이랑 unsafe 비교해보면 clone()콜 한번 빼고는 별 차이도 없어보이는데
clone() 한번이 메모리 할당임
https://godbolt.org/z/Pd8W9E3ea
이게 더 비교하기 편하네
clone충 등장
이런 애들이 자꾸 이상한 오해 퍼트리는거임 저거랑 성능 똑같은 safe 버전 코드가 있고 만약 없어도 대부분 차이 거의 안남
참고로 split_at하면 똑같고 slice 패턴으로 나눠도 똑같음 예시가 임의의 중간 원소들이였으면 safe 버전 존나 길어지니까 들어맞긴 할듯
String 타입이면 push_str을 쓰자
그게 안되는걸 모름?
String::add_assign = String::push_str
https://doc.rust-lang.org/src/alloc/string.rs.html#2215
길어서 안읽음 ㅅㄱ
한줄짜리 러스트 inline 함수도 못 읽네
["A","B"].join("")