Rust에서 스레드 안전은 보통 데이터 레이스가 일어나지 않는것을 말한다


대표적인 데이터 레이스의 예로 한 숫자를 여러 스레드에서 더하는것이 있다


open System.Threading let mutable n = 0 let rec foo = function | 0 -> () | x -> n <- n + 1 foo (x - 1) let bar () = foo 100000 let a = Thread bar let b = Thread bar a.Start() b.Start() a.Join() b.Join() printfn "%d" n


이 코드의 결과가 무엇이 나올지 아무도 모른다


이와같은 데이터 레이스는 여러 스레드에 공유된 참조가 동시에 접근해서 값을 수정할때 일어나는데


반대로 말하면 여러 스레드가 아니거나 공유되지 않거나 동시에 접근하지 않거나 값을 수정하지 않으면 데이터 레이스가 일어나지 않는다


그래서 Rust에선 4가지중 하나라도 들어맞는 코드를 짜도록 요구한다



1. 여러 스레드가 아닌경우


Rust에서 스레드 경계를 넘을수 있는 타입들은 `Send`를 구현하는데


애초에 스레드가 딱 하나라면 경계를 넘을 필요도 없고 동기화를 신경쓸 필요도 없다






그리고 !Send를 걸어버리면 항상 생성된 스레드 내에서만 존재한다고 생각할수있고 그걸 이용해서 더 공격적인 최적화도 가능하다


대표적으로 Rc는 아토믹 연산없는 참조 카운팅 스마트 포인터지만 스레드 경계를 넘을수 없으므로 스레드 안전하다






2. 공유를 하지 않는경우


스레드 경계를 넘더라도 그것이 참조가 아니라면 동시에 접근할일 자체가 없어진다


대부분의 타입은 참조 타입이 아니기에 무난하게 Send를 구현할수 있다



또한 Box와 &mut는 참조지만 여러개를 만드는것이 불가능하므로 T만 Send면 Send를 구현한다






3. 동시에 접근하지 않는경우 + 4. 수정하지 않는경우


공유된 참조는 소유권 규칙을 따라서 항상 내부타입의 &만을 얻을수있다

그리고 이 & 접근이 스레드 안전한가를 나타내는 트레잇이 바로 `Sync`다





대부분 타입들은 &를 통해 수정하는것이 불가능하므로 Sync를 구현한다



하지만 몇가지 예외가 있는데


일단 UnsafeCell로 대표되는 interior mutability 시리즈다





UnsafeCell은 스레드 경계를 넘을수는 있지만 &UnsafeCell은 넘을수 없다 공유된 참조가 스레드 안전하지 않기 때문이다


똑같이 Cell과 RefCell도 Send긴 하지만 Sync는 아니다



물론 모든 interior mutability를 가진 타입들이 !Sync인건 아니다



RwLock은 동기화된 접근을 제공하므로 Sync지만 읽기참조가 여러개 있을수 있어서 T가 Sync임을 요구하고




Mutex는 아얘 동시에 하나의 접근만 허용하므로 T가 Sync가 아니라도 Sync를 갖는다






이 4가지 경우에는 각각의 요구사항에 맞춰서 코드를 짜면 자동으로 스레드 안전한 코드가 나오게되고 컴파일이 성공하게 된다