022 연결된 목록을 통해 Rust에 안전하지 않은 단일 연결 목록이 필요한 이유 알아보기

소개하다

비디오 주소: https://www.bilibili.com/video/av78062009/
관련 소스 코드: https://github.com/anonymousGiga/Rust-link-list

세부

과거에는 안전한 Rust 프로그래밍을 사용하여 연결 목록을 구현했지만 구현하기가 정말 어려웠습니다. 이 섹션부터 안전하지 않은 프로그래밍 방식으로 연결된 목록을 구현하기 시작합니다.

스택과 대기열의 차이점은 스택은 선입선출 방식이고 대기열은 선입선출 방식이라는 것입니다. 푸시 및 팝 기능에 해당하는 두 가지 차이점은 다음과 같습니다.

//栈
input list:
[Some(ptr)] -> (A, Some(ptr)) -> (B, None)

stack push X:
[Some(ptr)] -> (X, Some(ptr)) -> (A, Some(ptr)) -> (B, None)

stack pop:
[Some(ptr)] -> (A, Some(ptr)) -> (B, None)


//队列
input list:
[Some(ptr)] -> (A, Some(ptr)) -> (B, None)

flipped push X:
[Some(ptr)] -> (A, Some(ptr)) -> (B, Some(ptr)) -> (X, None)

flipped pop:
[Some(ptr)] -> (A, Some(ptr)) -> (B, None)

연결 목록을 사용하여 스택과 대기열을 구현할 때 팝 및 푸시 기능을 순회해야 할 수도 있음을 발견했습니다. 그렇다면 효율성을 개선하고 순회를 줄이는 방법은 무엇일까요? 대답은 연결 리스트의 헤드와 테일을 따로 기록한다는 것입니다.

보다 완전한 단일 연결 목록을 시도하십시오.

연결된 목록의 꼬리를 기록하기 위해 연결 목록을 다음과 같이 재정의합니다.

ub struct List<'a, T> {
	head: Link<T>,
	tail: Option<&'a mut Node<T>>, //自己指向自己,都是可变引用,不允许存在
}

type Link<T> = Option<Box<Node<T>>>;

struct Node<T> {
	elem: T,
	next: Link<T>,
}

impl<'a, T> List<'a, T> {
	pub fn new() -> Self {
		List { head: None, tail: None }	
	}

	pub fn push(&'a mut self, elem: T) {
		let new_tail = Box::new(Node {
			elem: elem,
			next: None, 
		});
		
		let new_tail = match self.tail.take() {
			Some(old_tail) => {
				old_tail.next = Some(new_tail);
				old_tail.next.as_deref_mut()
			}
			None => {
				self.head = Some(new_tail);
				self.head.as_deref_mut()
			}
		}; 
		
		self.tail = new_tail;
	}

	pub fn pop(&'a mut self) -> Option<T> {
		self.head.take().map(|head| {
			let head = *head;
			self.head = head.next;

			if self.head.is_none() {
				self.tail = None;
			}

			head.elem
		})
	}
}

기존 문제

레코드 꼬리 위치가 있는 단일 연결 목록을 구현했을 때 컴파일에서 문제가 없음을 발견했습니다. 아래 테스트 코드를 추가해 보겠습니다.

#[cfg(test)]
mod tests {
	use super::List;

    #[test]
    fn basics() {
		let mut list = List::new();
		assert_eq!(list.pop(), None);
		list.push(1);
		list.push(2);
		list.push(3);
		assert_eq!(list.pop(), Some(1));
		assert_eq!(list.pop(), Some(2));
		list.push(4);
		list.push(5);
		assert_eq!(list.pop(), Some(3));
		assert_eq!(list.pop(), Some(4));
		assert_eq!(list.pop(), Some(5));
		assert_eq!(list.pop(), None);
    }
}

컴파일에서 오류가 발견되었습니다. 그 이유는 무엇입니까?

알고보니 리스트에서 정의된 꼬리도 변수고, 정의된 리스트도 변수고, 꼬리도 링크드 리스트의 일부로 자기 자신을 가리키는 변수 참조에 해당하고 자기 자신이 변수라는 것을 알 수 있습니다. 다중 가변 참조가 동일한 것을 가리키므로 오류가 보고됩니다.

저희 분석에 따르면 이 문제는 오해가 있는 것 같은데 어떻게 해결해야 할까요?
따라서 이 문제를 해결하기 위해 unsafe가 도입되었습니다.

Guess you like

Origin blog.csdn.net/lcloveyou/article/details/122774265