unsafe와 안전한 감싸기 문서의 구현 코드다.
//! unsafe를 안전한 함수로 감싸는 예: 슬라이스를 겹치지 않는 두 &mut로 나누는 split_at_mut,
//! 경계 검사를 미리 해 두고 검사 없이 읽는 sum_pairs. 모든 unsafe 블록에 // SAFETY: 주석을 달고
//! clippy::undocumented_unsafe_blocks와 unsafe_op_in_unsafe_fn을 deny로 켠다.
//! 의존성: 없음 (표준 라이브러리만)
//! 실행: python3 "_시스템/Rust 계획/rs_check.py" "Rust/3.개념집/30_unsafe-abstractions_impl.rs"
#![deny(clippy::undocumented_unsafe_blocks)]
#![deny(unsafe_op_in_unsafe_fn)]
use std::slice;
/// `values`를 `[0, mid)`와 `[mid, len)` 두 조각으로 나눠 둘 다 고칠 수 있게 돌려준다.
///
/// # Panics
/// `mid > values.len()`이면 panic한다.
pub fn split_at_mut<T>(values: &mut [T], mid: usize) -> (&mut [T], &mut [T]) {
let len = values.len();
// 불변식의 핵심: 이 검사가 아래 두 포인터 계산을 모두 범위 안에 묶는다.
assert!(mid <= len, "mid {mid} > len {len}");
let ptr = values.as_mut_ptr();
// SAFETY:
// - ptr은 `values`에서 왔으므로 len개의 T에 대해 유효하고 정렬되어 있다.
// - mid <= len을 위에서 확인했으므로 ptr.add(mid)는 같은 할당 안(끝 포함)에 있다.
// - 두 조각 [0, mid)와 [mid, len)은 겹치지 않으므로 두 &mut가 같은 원소를 가리키지 않는다.
// - 반환 수명은 `values`의 빌림에 묶이므로, 조각이 살아 있는 동안 원본을 다른 곳에서 쓸 수 없다.
unsafe {
(
slice::from_raw_parts_mut(ptr, mid),
slice::from_raw_parts_mut(ptr.add(mid), len - mid),
)
}
}
/// 인덱스 검사 없이 읽는다. 부르는 쪽이 조건을 지켜야 한다.
///
/// # Safety
/// `i < v.len()`이어야 한다.
pub unsafe fn get_unchecked_copy(v: &[u32], i: usize) -> u32 {
// SAFETY: 이 함수의 안전 조건(i < v.len())을 부르는 쪽이 지킨다고 약속했다.
unsafe { *v.get_unchecked(i) }
}
/// 짝을 이룬 원소의 합. 겉보기는 안전한 함수다: 경계는 한 번만 확인하고 안쪽에서는 검사하지 않는다.
pub fn sum_pairs(a: &[u32], b: &[u32]) -> Vec<u32> {
let n = a.len().min(b.len());
(0..n)
.map(|i| {
// SAFETY: i < n <= a.len()이고 i < n <= b.len()이다.
unsafe { get_unchecked_copy(a, i) + get_unchecked_copy(b, i) }
})
.collect()
}
fn main() {
let mut v = [1, 2, 3, 4, 5, 6];
let (left, right) = split_at_mut(&mut v, 2);
left[0] = 10;
right[0] = 30;
right[3] = 60;
assert_eq!(left.len() + right.len(), 6);
assert_eq!(v, [10, 2, 30, 4, 5, 60]);
println!("split_at_mut(2): {v:?}");
// 경계 사례: mid = 0, mid = len
let mut w = [7u8; 3];
let (a, b) = split_at_mut(&mut w, 0);
assert_eq!((a.len(), b.len()), (0, 3));
let (a, b) = split_at_mut(&mut w, 3);
assert_eq!((a.len(), b.len()), (3, 0));
// 표준 라이브러리 버전과 같은 결과
for mid in 0..=6 {
let mut x: Vec<i32> = (0..6).collect();
let mut y = x.clone();
{
let (p, q) = split_at_mut(&mut x, mid);
p.iter_mut().for_each(|e| *e *= 2);
q.iter_mut().for_each(|e| *e += 100);
}
{
let (p, q) = y.split_at_mut(mid);
p.iter_mut().for_each(|e| *e *= 2);
q.iter_mut().for_each(|e| *e += 100);
}
assert_eq!(x, y, "mid = {mid}");
}
// mid > len이면 unsafe에 닿기 전에 panic
std::panic::set_hook(Box::new(|_| {}));
let r = std::panic::catch_unwind(|| {
let mut z = [0; 2];
let _ = split_at_mut(&mut z, 3);
});
let _ = std::panic::take_hook();
assert!(r.is_err());
println!("mid > len: unsafe 블록 전에 panic");
assert_eq!(sum_pairs(&[1, 2, 3], &[10, 20]), vec![11, 22]);
assert_eq!(sum_pairs(&[], &[1]), Vec::<u32>::new());
println!("ALL CHECKS PASSED");
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn halves_are_independent() {
let mut v = vec![0u64; 10];
let (a, b) = split_at_mut(&mut v, 4);
a.fill(1);
b.fill(2);
assert_eq!(v.iter().filter(|&&x| x == 1).count(), 4);
assert_eq!(v.iter().filter(|&&x| x == 2).count(), 6);
}
#[test]
#[should_panic(expected = "mid 5 > len 4")]
fn mid_past_end_panics() {
let mut v = [0; 4];
let _ = split_at_mut(&mut v, 5);
}
#[test]
fn works_with_non_copy_types() {
let mut v = vec![String::from("a"), String::from("b")];
let (a, b) = split_at_mut(&mut v, 1);
std::mem::swap(&mut a[0], &mut b[0]);
assert_eq!(v, ["b", "a"]);
}
}