For an additional/alternative source, running the original post’s code through MIRI (in the playground under TOOLS→Miri) gives this feedback directly without any need to read through lots of documentation.
error: Undefined Behavior: dereferencing pointer failed: alloc1653 has size 4, so pointer to 1 byte starting at offset 4 is out-of-bounds
7 | let c = *current;
| ^^^^^^^^ dereferencing pointer failed: alloc1653 has size 4, so pointer to 1 byte starting at offset 4 is out-of-bounds
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE:
= note: inside `main` at src/main.rs:7:18: 7:26
The output up to the point of reaching UB is only
so clearly, since it did not appear here, the 0 we saw was just an arbitrary result of undefined behavior and doesn’t mean anything.
There is no place that says there is a null character (and I’ll discuss below why there cannot be such a place, without anyone needing to actually read the whole reference), so there needs be no place explicitly calling out that there isn’t.
Null-terminated strings are considered bad language design by some people anyways, for reasons like: determining their length is an O(n) operation, and you cannot create a sub-string without copying all the data. Rust strings can be sliced into sub-strings efficiently becausestr doesn’t need to end with a null, as demonstrated by multiple people in previous responses in this thread, which should be sufficient proof that there cannot be any place in the reference that says that strings are null-terminated.
If you mean your question more narrowly for only string literals (i.e. those things of type &'static str that are created from the "foo" syntax and are compiled into static memory), then, still – logically – nothing behind/terminating a string literal, since (as people have also demonstrated above) you – or any part of your code – cannot actually access the byte behind the last character without causing immediate undefined behavior. This doesn’t yet rule out that – perhaps – at runtime, every string literal is – in practice – followed by at least one 0… but such considerations would have nothing to do with the language Rust anymore. It would be a valid implementation strategy for the compiler, but it doesn’t matter, since any memory outside of the string literal cannot be accessed without causing undefined behavior.
assuming the C-style approach where you don’t pass some length information alongside it; but if you did that the null-termination would become unnecessary, leaving only disadvantages (besides possibly better C-interop) ↩︎
though I would be surprised if you couldn’t, with the correct length literal, come up with example code demonstrating that this implementation strategy is not the one taken by rustc+LLVM ↩︎
I’m wondering why nobody pointed that out earlier… or perhaps that, too, might’ve been subject to change for some reason, but the rust playground does AFAICT very consistently not print a 0 even for the original code in this thread; in all configurations: for and combination of stable/beta/nightly and debug/release. (At the moment, I’m getting either 10 or 100 printed, depending on debug vs. release mode.)
of course, since the code is already proven to produce UB anyways, this observation is technically entirely irrelevant, but at least it does – at least in my interpretation – even more clearly refute the original premise ↩︎
Yeah, I realized later the citations weren't about literals specifically, but I'd argue it's implicit given the runtime erasure of lifetimes, ability to subslice &str, type equality of leaked (&'static str) strings, no defined (non-UB) way to read past the end, etc. That said, I don't think a clarification in documentation somewhere for people coming from other languages would be a bad thing.
(Incidentally Rust has no spec or standard, and as such normative citations are scarce.)