← Hub Principal |
🦀

01: Doubly Linked List & Safe Pointers

Rust 2021 Edition
Sistemas de Baixo Nível & Memory Safety Módulo 01

Doubly Linked List Manual com Safe Pointers

Uma análise detalhada da estrutura de dados mais infame da engenharia em Rust. Por exigir que múltiplos nós mantenham referências mutáveis bidirecionais simultâneas para o mesmo elemento, a Lista Duplamente Ligada força o desenvolvedor a lidar diretamente com os limites do Borrow Checker, invariantes de memória e encapsulamento estrito de blocos unsafe.


01. O Paradoxo de Rust: Aliasing XOR Mutability

O pilar central da segurança de memória em Rust resume-se à regra fundamental de ponteiros: ou você possui várias referências imutáveis (&T), ou você possui uma única referência mutável exclusiva (&mut T).

Em uma lista duplamente ligada típica, se um nó intermediário B está entre A e C:

  • A.next aponta para B
  • C.prev aponta para B

Ambos precisam conseguir alterar B durante operações de inserção e remoção. Isso viola a exclusividade de empréstimo mutável da linguagem. Para resolver este paradoxo em nível de engenharia, existem três caminhos arquiteturais possíveis:

02. Decisão de Design: Por que descartamos Rc<RefCell<T>>?

1. Safe Abstraction

Rc<RefCell<Node<T>>>

Utiliza contagem de referências e checagem de empréstimo em tempo de execução.

❌ Alto overhead de memória por nó, overhead de CPU e risco de memory leaks por ciclos de referência sem Weak<T>.
2. Índices de Vetor

Vec<Node<T>> + usize

Aloca todos os nós em um vetor central contíguo e usa índices numéricos como ponteiros.

⚠️ Excelente localidade de cache, porém fragmenta memória na deleção e exige freelist manual de índices vazios.
3. Nossa Escolha (Idêntica à Stdlib)

NonNull<Node<T>>

Ponteiros crus encapsulados em tipos covariantes com otimização em tempo de compilação.

✅ Zero custo extra em memória, performance equivalente a C/C++ e encapsulamento 100% seguro na API pública.

03. Anatomia Interna & Smart Primitives

NonNull<T> e Null Pointer Optimization (NPO)

Diferente de um ponteiro bruto *mut T (que pode ser nulo), o tipo NonNull<T> assegura ao compilador que seu valor nunca é zero. Isso ativa a Null Pointer Optimization (NPO) do rustc: o enum Option<NonNull<T>> utiliza a representação de bits 0x0 para significar None. Consequentemente, o tamanho de Option<NonNull<T>> é de exatamente 8 bytes em arquiteturas de 64-bit (o mesmo tamanho de um ponteiro cru simples), sem o overhead de uma tag de discriminação.

PhantomData e o Drop Checker (eyepatch)

Como nossa struct utiliza ponteiros crus (NonNull), o compilador do Rust assume que ela não é dona dos dados apontados, mas apenas os observa. O marcador _marker: PhantomData<Box<Node<T>>> informa formalmente ao Drop Checker e ao analisador de variância que a struct DoublyLinkedList<T> é a proprietária legítima dos nós no Heap e é encarregada de descartá-los de forma segura.

Destruição Iterativa via Drop Trait (Evitando Stack Overflow)

Se a desalocação fosse baseada no destrutor recursivo padrão, a destruição de uma lista de 500.000 elementos geraria 500.000 frames de chamada na pilha de execução (drop() -> drop() -> drop()), estourando a memória de stack (Stack Overflow). Implementamos explicitamente a trait Drop com um loop iterativo while self.pop_front().is_some() {}, garantindo consumo constante de stack space ($O(1)$) ao desalocar cada nó individual da Heap.

04. Complexidade Assintótica & Hardware Reality

Operação Complexidade de Tempo Uso de Memória Detalhe Operacional
push_front / push_back O(1) O(1) Alocação direta de nó isolado via Box::into_raw
pop_front / pop_back O(1) O(1) Reassociação de ponteiros e desalocação imediata
peek_front / peek_back O(1) O(1) Empréstimo imutável (&T) sem overhead de cópia
drop (Desalocação) O(n) O(1) stack Loop iterativo seguro com desalocação nó a nó
💡 Por que a Linked List raramente é usada no mundo real?

Apesar do tempo de inserção $O(1)$ nas bordas, cada nó está espalhado em endereços aleatórios do Heap. CPUs modernas contam com linhas de cache L1/L2/L3 e prefetchers de hardware projetados para blocos sequenciais contíguos de memória. Navegar por nós ponteirados resulta em frequentes Cache Misses. Em 99% das aplicações reais em Rust, estruturas como Vec ou VecDeque superam uma lista ligada em ordens de magnitude na velocidade prática de execução.

cargo run --bin doubly_linked_list
Ambiente pronto. Clique em "Compilar & Executar" para rodar o código Rust.