Skip to content

Commit ec7966a

Browse files
committed
Rollup merge of #28869 - alexcrichton:allocator-dox, r=steveklabnik
This adds a chapter to the nightly section of the book on leveraging and implementing the `#![allocator]` attribute to write custom allocators as well as explaining the current situation with allocators.
2 parents 8c19a88 + 9a38747 commit ec7966a

File tree

2 files changed

+170
-0
lines changed

2 files changed

+170
-0
lines changed

src/doc/trpl/SUMMARY.md

+1
Original file line numberDiff line numberDiff line change
@@ -68,5 +68,6 @@
6868
* [Box Syntax and Patterns](box-syntax-and-patterns.md)
6969
* [Slice Patterns](slice-patterns.md)
7070
* [Associated Constants](associated-constants.md)
71+
* [Custom Allocators](custom-allocators.md)
7172
* [Glossary](glossary.md)
7273
* [Bibliography](bibliography.md)

src/doc/trpl/custom-allocators.md

+169
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
% Custom Allocators
2+
3+
Allocating memory isn't always the easiest thing to do, and while Rust generally
4+
takes care of this by default it often becomes necessary to customize how
5+
allocation occurs. The compiler and standard library currently allow switching
6+
out the default global allocator in use at compile time. The design is currently
7+
spelled out in [RFC 1183][rfc] but this will walk you through how to get your
8+
own allocator up and running.
9+
10+
[rfc]: https://github.com/rust-lang/rfcs/blob/master/text/1183-swap-out-jemalloc.md
11+
12+
# Default Allocator
13+
14+
The compiler currently ships two default allocators: `alloc_system` and
15+
`alloc_jemalloc` (some targets don't have jemalloc, however). These allocators
16+
are just normal Rust crates and contain an implementation of the routines to
17+
allocate and deallocate memory. The standard library is not compiled assuming
18+
either one, and the compiler will decide which allocator is in use at
19+
compile-time depending on the type of output artifact being produced.
20+
21+
Binaries generated by the compiler will use `alloc_jemalloc` by default (where
22+
available). In this situation the compiler "controls the world" in the sense of
23+
it has power over the final link. Primarily this means that the allocator
24+
decision can be left up the compiler.
25+
26+
Dynamic and static libraries, however, will use `alloc_system` by default. Here
27+
Rust is typically a 'guest' in another application or another world where it
28+
cannot authoritatively decide what allocator is in use. As a result it resorts
29+
back to the standard APIs (e.g. `malloc` and `free`) for acquiring and releasing
30+
memory.
31+
32+
# Switching Allocators
33+
34+
Although the compiler's default choices may work most of the time, it's often
35+
necessary to tweak certain aspects. Overriding the compiler's decision about
36+
which allocator is in use is done simply by linking to the desired allocator:
37+
38+
```rust,no_run
39+
#![feature(alloc_system)]
40+
41+
extern crate alloc_system;
42+
43+
fn main() {
44+
let a = Box::new(4); // allocates from the system allocator
45+
println!("{}", a);
46+
}
47+
```
48+
49+
In this example the binary generated will not link to jemalloc by default but
50+
instead use the system allocator. Conversely to generate a dynamic library which
51+
uses jemalloc by default one would write:
52+
53+
```rust,no_run
54+
#![feature(alloc_jemalloc)]
55+
#![crate_type = "dylib"]
56+
57+
extern crate alloc_jemalloc;
58+
59+
pub fn foo() {
60+
let a = Box::new(4); // allocates from jemalloc
61+
println!("{}", a);
62+
}
63+
# fn main() {}
64+
```
65+
66+
# Writing a custom allocator
67+
68+
Sometimes even the choices of jemalloc vs the system allocator aren't enough and
69+
an entirely new custom allocator is required. In this you'll write your own
70+
crate which implements the allocator API (e.g. the same as `alloc_system` or
71+
`alloc_jemalloc`). As an example, let's take a look at a simplified and
72+
annotated version of `alloc_system`
73+
74+
```rust,no_run
75+
# // only needed for rustdoc --test down below
76+
# #![feature(lang_items)]
77+
// The compiler needs to be instructed that this crate is an allocator in order
78+
// to realize that when this is linked in another allocator like jemalloc should
79+
// not be linked in
80+
#![feature(allocator)]
81+
#![allocator]
82+
83+
// Allocators are not allowed to depend on the standard library which in turn
84+
// requires an allocator in order to avoid circular dependencies. This crate,
85+
// however, can use all of libcore.
86+
#![feature(no_std)]
87+
#![no_std]
88+
89+
// Let's give a unique name to our custom allocator
90+
#![crate_name = "my_allocator"]
91+
#![crate_type = "rlib"]
92+
93+
// Our system allocator will use the in-tree libc crate for FFI bindings. Note
94+
// that currently the external (crates.io) libc cannot be used because it links
95+
// to the standard library (e.g. `#![no_std]` isn't stable yet), so that's why
96+
// this specifically requires the in-tree version.
97+
#![feature(libc)]
98+
extern crate libc;
99+
100+
// Listed below are the five allocation functions currently required by custom
101+
// allocators. Their signatures and symbol names are not currently typechecked
102+
// by the compiler, but this is a future extension and are required to match
103+
// what is found below.
104+
//
105+
// Note that the standard `malloc` and `realloc` functions do not provide a way
106+
// to communicate alignment so this implementation would need to be improved
107+
// with respect to alignment in that aspect.
108+
109+
#[no_mangle]
110+
pub extern fn __rust_allocate(size: usize, _align: usize) -> *mut u8 {
111+
unsafe { libc::malloc(size as libc::size_t) as *mut u8 }
112+
}
113+
114+
#[no_mangle]
115+
pub extern fn __rust_deallocate(ptr: *mut u8, _old_size: usize, _align: usize) {
116+
unsafe { libc::free(ptr as *mut libc::c_void) }
117+
}
118+
119+
#[no_mangle]
120+
pub extern fn __rust_reallocate(ptr: *mut u8, _old_size: usize, size: usize,
121+
_align: usize) -> *mut u8 {
122+
unsafe {
123+
libc::realloc(ptr as *mut libc::c_void, size as libc::size_t) as *mut u8
124+
}
125+
}
126+
127+
#[no_mangle]
128+
pub extern fn __rust_reallocate_inplace(_ptr: *mut u8, old_size: usize,
129+
_size: usize, _align: usize) -> usize {
130+
old_size // this api is not supported by libc
131+
}
132+
133+
#[no_mangle]
134+
pub extern fn __rust_usable_size(size: usize, _align: usize) -> usize {
135+
size
136+
}
137+
138+
# // just needed to get rustdoc to test this
139+
# fn main() {}
140+
# #[lang = "panic_fmt"] fn panic_fmt() {}
141+
# #[lang = "eh_personality"] fn eh_personality() {}
142+
```
143+
144+
After we compile this crate, it can be used as follows:
145+
146+
```rust,ignore
147+
extern crate my_allocator;
148+
149+
fn main() {
150+
let a = Box::new(8); // allocates memory via our custom allocator crate
151+
println!("{}", a);
152+
}
153+
```
154+
155+
# Custom allocator limitations
156+
157+
There are a few restrictions when working with custom allocators which may cause
158+
compiler errors:
159+
160+
* Any one artifact may only be linked to at most one allocator. Binaries,
161+
dylibs, and staticlibs must link to exactly one allocator, and if none have
162+
been explicitly chosen the compiler will choose one. On the other than rlibs
163+
do not need to link to an allocator (but still can).
164+
165+
* A consumer of an allocator is tagged with `#![needs_allocator]` (e.g. the
166+
`liballoc` crate currently) and an `#[allocator]` crate cannot transitively
167+
depend on a crate which needs an allocator (e.g. circular dependencies are not
168+
allowed). This basically means that allocators must restrict themselves to
169+
libcore currently.

0 commit comments

Comments
 (0)