mem::replace và mem::take
std::mem::replace và std::mem::take là hai functions hữu ích giúp thay thế giá trị trong một mutable reference mà không cần clone, đặc biệt hữu dụng khi làm việc với ownership.
mem::replace
mem::replace lấy giá trị hiện tại và thay thế bằng giá trị mới, trả về giá trị cũ:
use std::mem;
fn main() {
let mut v = vec![1, 2, 3];
let old_v = mem::replace(&mut v, vec![4, 5, 6]);
println!("Old: {:?}", old_v); // [1, 2, 3]
println!("New: {:?}", v); // [4, 5, 6]
}
Signature
#![allow(unused)]
fn main() {
pub fn replace<T>(dest: &mut T, src: T) -> T
}
mem::take
mem::take là shorthand cho mem::replace(&mut dest, Default::default()):
use std::mem;
fn main() {
let mut v = vec![1, 2, 3];
let old_v = mem::take(&mut v);
println!("Old: {:?}", old_v); // [1, 2, 3]
println!("New: {:?}", v); // []
}
Signature
#![allow(unused)]
fn main() {
pub fn take<T: Default>(dest: &mut T) -> T
}
Use Cases
1. Modify enum variants
Một trong những use case phổ biến nhất là khi cần modify một enum variant:
use std::mem;
enum State {
Active { count: u32 },
Inactive,
}
impl State {
fn increment(&mut self) {
// Lấy ownership của self để pattern match
let old_state = mem::take(self);
*self = match old_state {
State::Active { count } => State::Active { count: count + 1 },
State::Inactive => State::Active { count: 1 },
};
}
}
fn main() {
let mut state = State::Inactive;
state.increment();
if let State::Active { count } = state {
println!("Count: {}", count); // Count: 1
}
}
Nếu không dùng mem::take, bạn sẽ gặp borrow checker error:
#![allow(unused)]
fn main() {
// ❌ Compile error!
fn increment_wrong(&mut self) {
*self = match self {
// Error: cannot move out of `*self` which is behind a mutable reference
State::Active { count } => State::Active { count: count + 1 },
State::Inactive => State::Active { count: 1 },
};
}
}
2. Implement methods on structs với owned fields
use std::mem;
struct Buffer {
data: Vec<u8>,
}
impl Buffer {
fn process(&mut self) -> Vec<u8> {
// Lấy data ra để xử lý, thay bằng empty vec
let data = mem::take(&mut self.data);
// Process data
let processed = data.into_iter()
.map(|b| b.wrapping_add(1))
.collect();
processed
}
}
fn main() {
let mut buffer = Buffer {
data: vec![1, 2, 3],
};
let result = buffer.process();
println!("Processed: {:?}", result); // [2, 3, 4]
println!("Buffer now: {:?}", buffer.data); // []
}
3. Swap values
Sử dụng mem::replace để swap values:
use std::mem;
fn main() {
let mut a = 5;
let mut b = 10;
// Swap using mem::replace
let temp = mem::replace(&mut a, mem::replace(&mut b, a));
println!("a: {}, b: {}", a, b); // a: 10, b: 5
// Hoặc đơn giản hơn, dùng std::mem::swap
mem::swap(&mut a, &mut b);
println!("a: {}, b: {}", a, b); // a: 5, b: 10
}
4. Avoid clone trong loops
use std::mem;
struct Node {
value: i32,
next: Option<Box<Node>>,
}
impl Node {
fn into_values(self) -> Vec<i32> {
let mut values = Vec::new();
let mut current = Some(Box::new(self));
while let Some(mut node) = current {
values.push(node.value);
// Take ownership of next without cloning
current = mem::take(&mut node.next);
}
values
}
}
fn main() {
let list = Node {
value: 1,
next: Some(Box::new(Node {
value: 2,
next: Some(Box::new(Node {
value: 3,
next: None,
})),
})),
};
let values = list.into_values();
println!("{:?}", values); // [1, 2, 3]
}
5. Working with Option
use std::mem;
struct Cache {
data: Option<String>,
}
impl Cache {
fn take_data(&mut self) -> Option<String> {
mem::take(&mut self.data)
}
fn replace_data(&mut self, new_data: String) -> Option<String> {
mem::replace(&mut self.data, Some(new_data))
}
}
fn main() {
let mut cache = Cache {
data: Some("Hello".to_string()),
};
// Lấy data ra khỏi cache
let data = cache.take_data();
println!("Taken: {:?}", data); // Some("Hello")
println!("Cache: {:?}", cache.data); // None
// Replace với data mới
let old = cache.replace_data("World".to_string());
println!("Old: {:?}", old); // None
println!("Cache: {:?}", cache.data); // Some("World")
}
mem::replace vs clone
#![allow(unused)]
fn main() {
use std::mem;
fn process_with_clone(data: &mut Vec<i32>) {
let copy = data.clone(); // ❌ Expensive: allocates new memory
// Process copy...
}
fn process_with_replace(data: &mut Vec<i32>) {
let owned = mem::replace(data, Vec::new()); // ✅ No allocation
// Process owned...
}
}
So sánh mem::take vs mem::replace
| Feature | mem::take | mem::replace |
|---|---|---|
| Yêu cầu | T: Default | Bất kỳ T nào |
| Giá trị thay thế | Default::default() | Giá trị tùy chỉnh |
| Use case | Khi muốn giá trị mặc định | Khi cần giá trị cụ thể |
Khi nào nên dùng?
✅ Nên dùng khi:
- Cần move value ra khỏi mutable reference
- Muốn tránh clone expensive values
- Làm việc với enums có multiple variants
- Implement state machines
❌ Không cần dùng khi:
- Type implement
Copy - Có thể dùng
Option::take()(choOptiontypes) - Clone là acceptable
Pattern thực tế: Option::take()
Với Option<T>, có thể dùng method take() thay vì mem::take():
#![allow(unused)]
fn main() {
struct Handler {
resource: Option<String>,
}
impl Handler {
fn consume(&mut self) -> Option<String> {
// Cả hai cách đều work
// Cách 1: Option::take()
self.resource.take()
// Cách 2: mem::take() - ít common hơn cho Option
// std::mem::take(&mut self.resource)
}
}
}
Best Practices
- Prefer
mem::takekhi cóDefault: Ngắn gọn hơnmem::replace - Document side effects: Ghi rõ function modifies state
- Consider
Option::take(): ChoOptiontypes - Combine với pattern matching: Rất hữu dụng với enums