Skip to content

Commit 07783d1

Browse files
committed
deprecating CStr::as_ptr in favor of with_ptr
1 parent 8fb8742 commit 07783d1

File tree

1 file changed

+126
-0
lines changed

1 file changed

+126
-0
lines changed

text/0000-cstr-with-ptr.md

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
- Feature Name: `cstr_with_ptr`
2+
- Start Date: 2016-06-06
3+
- RFC PR: (leave this empty)
4+
- Rust Issue: (leave this empty)
5+
6+
# Summary
7+
[summary]: #summary
8+
9+
Deprecate `CStr::as_ptr`, which returns a raw pointer and thereby invites unsound code, in favor of
10+
a new method, `CStr::with_ptr`, which passes the pointer to a user-provided closure.
11+
12+
# Motivation
13+
[motivation]: #motivation
14+
15+
`CString` wraps a C string and provides easy, safe access to the raw pointer to the string, for
16+
FFI interop. The safety is correct, as there's nothing unsound about having a raw pointer around
17+
until you dereference it. However, raw pointers are not tracked by the borrow checker, and once you
18+
have one it's as easy in Rust as it is in C to keep it around too long (namely, after the backing
19+
`CString` is dropped) and have a dangling pointer, which leads to unsound code.
20+
21+
There are [hundreds of projects](https://users.rust-lang.org/t/you-should-stop-telling-people-that-safe-rust-is-always-safe/6094/7)
22+
including a call like `CString::new(...).unwrap().as_ptr()`, which is evidence that this UB
23+
mistake is widespread.
24+
25+
By changing the API from a method _returning_ a raw pointer to one that runs a user-provided
26+
closure, we make it easier to write sound code than to write unsound code. Sure, you can still
27+
have the pointer escape the closure (see [Drawbacks](#drawbacks)), but it's a clear choice. The
28+
API change tips the scales towards soundness.
29+
30+
# Detailed design
31+
[design]: #detailed-design
32+
33+
1. Deprecate `CStr::as_ptr`, with the following message: "too easy to misuse because it returns a
34+
raw pointer; use `with_ptr` instead". The method is stable, so it will not be removed in any 1.x
35+
release.
36+
37+
2. Add the following method to `CStr`:
38+
39+
```rust
40+
/// Calls the provided closure, passing the inner pointer to this C string.
41+
///
42+
/// The pointer will be valid for as long as `self` is and points to a contiguous region of
43+
/// memory terminated with a 0 byte to represent the end of the string.
44+
#[allow(deprecated)] // for the as_ptr call
45+
fn with_ptr<F: FnOnce(*const c_char)>(&self, f: F) {
46+
f(self.as_ptr());
47+
}
48+
```
49+
50+
For example usage see [this playpen](https://play.rust-lang.org/?gist=b6b1495ebee03fea679e95acb6b51ed6).
51+
52+
3. Modify the `CStr` and `CString` examples that use `as_ptr` to use `with_ptr` instead.
53+
54+
`CString::new` has the following example code:
55+
56+
```rust
57+
use std::ffi::CString;
58+
use std::os::raw::c_char;
59+
60+
extern { fn puts(s: *const c_char); }
61+
62+
fn main() {
63+
let to_print = CString::new("Hello!").unwrap();
64+
unsafe {
65+
puts(to_print.as_ptr());
66+
}
67+
}
68+
```
69+
70+
Under this proposal, it would change to:
71+
72+
```rust
73+
use std::ffi::CString;
74+
use std::os::raw::c_char;
75+
76+
extern { fn puts(s: *const c_char); }
77+
78+
fn main() {
79+
let to_print = CString::new("Hello!").unwrap();
80+
to_print.with_ptr(|p| unsafe {
81+
puts(p);
82+
});
83+
}
84+
```
85+
86+
There are nearly identical examples on `CString` and `CStr` themselves which would change in the
87+
same way.
88+
89+
# Drawbacks
90+
[drawbacks]: #drawbacks
91+
92+
- It deprecates another stable method, which contributes to perception of API churn.
93+
- It adds surface area to the API of `CStr`.
94+
- It's still rather easy to circumvent the help and write unsound code by "leaking" the pointer out
95+
of the closure:
96+
97+
```rust
98+
let mut ptr = ptr::null();
99+
{
100+
let s = CString::new("foo").unwrap();
101+
s.with_ptr(|p| { ptr = p; });
102+
}
103+
/* ptr is now dangling */
104+
```
105+
106+
# Alternatives
107+
[alternatives]: #alternatives
108+
109+
- Do nothing. It remains very easy to write unsound code using `CString` and `CStr::as_ptr`.
110+
- Move the `temporary_cstring_as_ptr` lint, which warns on the most common way to write said unsound
111+
code (calling `as_ptr` on a temporary `CString`) from Clippy to rustc.
112+
113+
# Unresolved questions
114+
[unresolved]: #unresolved-questions
115+
116+
- Should `with_ptr` allow the closure to return a value? It could be
117+
118+
```rust
119+
fn with_ptr<F: FnOnce(*const c_char) -> O, O>(&self, f: F) -> O {
120+
f(self.as_ptr())
121+
}
122+
```
123+
124+
which might be convenient but would make it easier to "leak" the pointer (as easy as
125+
`let ptr = s.with_ptr(|p| p);`).
126+

0 commit comments

Comments
 (0)