d5b27e407f
Clarify that copied allocators must behave the same Currently, the safety documentation for `Allocator` says that a cloned or moved allocator must behave the same as the original. However, it does not specify that a copied allocator must behave the same, and it's possible to construct an allocator that permits being moved or cloned, but sometimes produces a new allocator when copied. <details> <summary>Contrived example which results in a Miri error</summary> ```rust #![feature(allocator_api, once_cell, strict_provenance)] use std::{ alloc::{AllocError, Allocator, Global, Layout}, collections::HashMap, hint, marker::PhantomPinned, num::NonZeroUsize, pin::Pin, ptr::{addr_of, NonNull}, sync::{LazyLock, Mutex}, }; mod source_allocator { use super::*; // `SourceAllocator` has 3 states: // - invalid value: is_cloned == false, source != self.addr() // - source value: is_cloned == false, source == self.addr() // - cloned value: is_cloned == true pub struct SourceAllocator { is_cloned: bool, source: usize, _pin: PhantomPinned, } impl SourceAllocator { // Returns a pinned source value (pointing to itself). pub fn new_source() -> Pin<Box<Self>> { let mut b = Box::new(Self { is_cloned: false, source: 0, _pin: PhantomPinned, }); b.source = b.addr(); Box::into_pin(b) } fn addr(&self) -> usize { addr_of!(*self).addr() } // Invalid values point to source 0. // Source values point to themselves. // Cloned values point to their corresponding source. fn source(&self) -> usize { if self.is_cloned || self.addr() == self.source { self.source } else { 0 } } } // Copying an invalid value produces an invalid value. // Copying a source value produces an invalid value. // Copying a cloned value produces a cloned value with the same source. impl Copy for SourceAllocator {} // Cloning an invalid value produces an invalid value. // Cloning a source value produces a cloned value with that source. // Cloning a cloned value produces a cloned value with the same source. impl Clone for SourceAllocator { fn clone(&self) -> Self { if self.is_cloned || self.addr() != self.source { *self } else { Self { is_cloned: true, source: self.source, _pin: PhantomPinned, } } } } static SOURCE_MAP: LazyLock<Mutex<HashMap<NonZeroUsize, usize>>> = LazyLock::new(Default::default); // SAFETY: Wraps `Global`'s methods with additional tracking. // All invalid values share blocks with each other. // Each source value shares blocks with all cloned values pointing to it. // Cloning an allocator always produces a compatible allocator: // - Cloning an invalid value produces another invalid value. // - Cloning a source value produces a cloned value pointing to it. // - Cloning a cloned value produces another cloned value with the same source. // Moving an allocator always produces a compatible allocator: // - Invalid values remain invalid when moved. // - Source values cannot be moved, since they are always pinned to the heap. // - Cloned values keep the same source when moved. unsafe impl Allocator for SourceAllocator { fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> { let mut map = SOURCE_MAP.lock().unwrap(); let block = Global.allocate(layout)?; let block_addr = block.cast::<u8>().addr(); map.insert(block_addr, self.source()); Ok(block) } unsafe fn deallocate(&self, block: NonNull<u8>, layout: Layout) { let mut map = SOURCE_MAP.lock().unwrap(); let block_addr = block.addr(); // SAFETY: `block` came from an allocator that shares blocks with this allocator. if map.remove(&block_addr) != Some(self.source()) { hint::unreachable_unchecked() } Global.deallocate(block, layout) } } } use source_allocator::SourceAllocator; // SAFETY: `alloc1` and `alloc2` must share blocks. unsafe fn test_same(alloc1: &SourceAllocator, alloc2: &SourceAllocator) { let ptr = alloc1.allocate(Layout:🆕:<i32>()).unwrap(); alloc2.deallocate(ptr.cast(), Layout:🆕:<i32>()); } fn main() { let orig = &*SourceAllocator::new_source(); let orig_cloned1 = &orig.clone(); let orig_cloned2 = &orig.clone(); let copied = &{ *orig }; let copied_cloned1 = &copied.clone(); let copied_cloned2 = &copied.clone(); unsafe { test_same(orig, orig_cloned1); test_same(orig_cloned1, orig_cloned2); test_same(copied, copied_cloned1); test_same(copied_cloned1, copied_cloned2); test_same(orig, copied); // error } } ``` </details> This could result in issues in the future for algorithms that specialize on `Copy` types. Right now, nothing in the standard library that depends on `Allocator + Clone` is susceptible to this issue, but I still think it would make sense to specify that copying an allocator is always as valid as cloning it. |
||
---|---|---|
.cargo | ||
.github | ||
.vscode | ||
assets | ||
bench_data | ||
crates | ||
docs | ||
editors/code | ||
lib | ||
xtask | ||
.editorconfig | ||
.git-blame-ignore-revs | ||
.gitattributes | ||
.gitignore | ||
Cargo.lock | ||
Cargo.toml | ||
LICENSE-APACHE | ||
LICENSE-MIT | ||
PRIVACY.md | ||
README.md | ||
rustfmt.toml | ||
triagebot.toml |
rust-analyzer is a modular compiler frontend for the Rust language. It is a part of a larger rls-2.0 effort to create excellent IDE support for Rust.
Quick Start
https://rust-analyzer.github.io/manual.html#installation
Documentation
If you want to contribute to rust-analyzer or are just curious about how things work under the hood, check the ./docs/dev folder.
If you want to use rust-analyzer's language server with your editor of choice, check the manual folder. It also contains some tips & tricks to help you be more productive when using rust-analyzer.
Security and Privacy
See the corresponding sections of the manual.
Communication
For usage and troubleshooting requests, please use "IDEs and Editors" category of the Rust forum:
https://users.rust-lang.org/c/ide/14
For questions about development and implementation, join rust-analyzer working group on Zulip:
https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Frust-analyzer
Quick Links
- Website: https://rust-analyzer.github.io/
- Metrics: https://rust-analyzer.github.io/metrics/
- API docs: https://rust-lang.github.io/rust-analyzer/ide/
- Changelog: https://rust-analyzer.github.io/thisweek
License
rust-analyzer is primarily distributed under the terms of both the MIT license and the Apache License (Version 2.0).
See LICENSE-APACHE and LICENSE-MIT for details.