Skip to content

str::as_ptr

str methods · Strings

Level: reference · for working programmers

One line: The raw address of the first byte, as *const u8 — for handing text to C, and for nothing else in ordinary code.

pub const fn as_ptr(&self) -> *const u8

Stable since 1.0.0. Usable in a const context.

A &str is a pointer and a length; as_ptr is the pointer half. Reading through it is unsafe, and two invariants come with it.

It is not NUL-terminated. A Rust string knows its length, so it does not need a terminator and does not have one. Passing s.as_ptr() to a C function expecting char * reads past the end of the string. Use CString when a C API wants a terminated string.

The pointer is only valid while the string is. Keeping it past the owner's drop, or past a push_str that reallocates, leaves it dangling — and nothing warns you, because a raw pointer is not tracked by the borrow checker.

In safe code the honest uses are narrow: FFI, and identity checks like "is this slice a view into that buffer". For the second, substr_range answers the same question without raw pointers.

Example

str_as_ptr.rs in full — pasted here by tools/run_examples.py from the file CI compiles and runs.

fn main() {
    let s = "hello";

    // Addresses differ every run, so prove the layout with *derived* arithmetic.
    let start = s.as_ptr() as usize;
    let third = s[2..].as_ptr() as usize;
    println!("a slice at byte 2 starts {} bytes along", third - start);

    // The pointer plus the length is the whole of a &str.
    let rebuilt = unsafe { std::slice::from_raw_parts(s.as_ptr(), s.len()) };
    println!("{:?}", std::str::from_utf8(rebuilt));

    // No terminator: the length is carried beside the pointer, not in the
    // bytes, which is why a C API needs CString rather than this pointer.
    println!("len is carried separately: {}", s.len());

    // Growing past the capacity gets a new buffer, so any pointer taken
    // before it is stale. Capacity is the deterministic signal; whether the
    // address itself changes is up to the allocator.
    let mut owned = String::with_capacity(4);
    owned.push_str("abcd");
    let before = owned.capacity();
    owned.push_str("efgh");
    println!("capacity {} -> {}", before, owned.capacity());
}

Verified output of str_as_ptr.rs — regenerated by tools/run_examples.py, never hand-typed.

a slice at byte 2 starts 2 bytes along
Ok("hello")
len is carried separately: 5
capacity 4 -> 8

See also

str::as_ptr in the standard library ↗

Po polsku

as_ptr zwraca surowy wskaźnik (raw pointer) *const u8 na pierwszy bajt — czyli tylko połowę tego, czym jest &str, bo druga połowa to długość trzymana obok wskaźnika, a nie w danych. Dla kogoś, kto przychodzi z C, to jest właśnie ta pułapka: tam łańcuch kończy się bajtem zerowym, tutaj żadnego terminatora nie ma, więc s.as_ptr() przekazany funkcji oczekującej char * czyta poza koniec tekstu — do FFI służy CString, nie ten wskaźnik. Druga pułapka jest cichsza: surowego wskaźnika nie pilnuje borrow checker, więc nic nie krzyknie, gdy przeżyje on swój łańcuch albo realokację po push_str — dostajesz zwisający wskaźnik bez jednego ostrzeżenia. W bezpiecznym kodzie prawie zawsze chodziło ci o as_bytes.

Szukaj po polsku: surowy wskaźnik · wskaźnik plus długość · rust str as_ptr · rust CString null terminated FFI · rust raw pointer dangling