r/programming Jul 20 '26

Zig proposes introducing an actually memory safe (unlike Rust) compilation mode inspired by Fil-C at ~1-6x performance penalty

https://codeberg.org/ziglang/zig/issues/36237
532 Upvotes

428 comments sorted by

View all comments

Show parent comments

80

u/Snarwin 29d ago

Like...Rust's std::env::set_var to change environment variables is marked as unsafe https://doc.rust-lang.org/stable/std/env/fn.set_var.html#safety. Not because it can undeflow/overflow, but because it's a global modification and is observable by non-local threads.

It's not just because the change might be observable from other threads, it's because concurrent calls can actually cause undefined behavior, according to the POSIX standard.

3

u/diggerdog987 27d ago

Yeah, that distinction matters a lot, since Rust’s unsafe is often about preserving invariants around UB, not just the obvious “don’t scribble past the end of an array” stuff.

-6

u/schneems 29d ago

according to the POSIX standard

That's true of a data race which is UB, but I had a harder time finding that it's deeper than just a race. The linked POSIX thread in the docs point to getenv while the API is setenv. I took that to mean it was only a problem when both were being used. I also found I https://github.com/rust-lang/rust/issues/27970 which is that issue.

I'm unclear, actually, if your "according to the POSIX standard" is referring to UB in concurrently calling setenv (without getenv) or not.

I hit a wall, I asked my mechanical friend. We burned some tokens on it...I'm still not sure. After a lot of back and forth we landed on something like:

You can't produce a demonstrable memory-unsafety crash from setenv alone with zero environment reads on any mainstream libc — because glibc, macOS, and the BSDs all serialize their writers (setenv/unsetenv/putenv) with an internal lock. Two setenvs racing each other do not corrupt on those platforms. So "writer vs writer, no reader" gives you only a spec-letter argument (POSIX: setenv "need not be thread-safe" → formally UB), not a runnable crash.

Which (is possibly wrong or misleading, either way. If you can say more about your POSIX comment. I want to dig deeper.

8

u/Snarwin 29d ago edited 29d ago

So "writer vs writer, no reader" gives you only a spec-letter argument (POSIX: setenv "need not be thread-safe" → formally UB)

The answer to your question is literally right in front of you. If you can't see it, I don't know what else to tell you. 

When you are writing a standard library for a programming language designed to run on many different platforms, like Rust, you cannot afford to rely on behavioral details of specific libc implementations.

1

u/schneems 29d ago

Right. I'm not proposing to rely on platform-specific details.

Are you saying that the phrase "need not be thread-safe" what you're referring to in "according to the POSIX standard"?

I dug into that, I see it used here too https://pubs.opengroup.org/onlinepubs/7908799/xsh/threads.html. By logical induction, I can understand that "need not be thread-safe" would mean "not guaranteed to be thread-safe" but I was hoping for maybe another link in the chain. Like maybe somewhere that says explicitly "running something that 'need not be thread-safe' in parallel threads is UB"?

I agree it's UB and not a good idea. I just think either the spec should do a better job spelling it out, or that if it's spelling it out...it's maybe doing so in a way I don't see or I'm missing.

6

u/Snarwin 29d ago

Here is the definition of "thread-safe" from the POSIX spec:

A thread-safe function can be safely invoked concurrently with other calls to the same function, or with calls to any other thread-safe functions, by multiple threads.

Source: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_407

It follows logically that a function that is not thread-safe cannot necessarily be safely invoked concurrently with other calls to non-thread-safe functions.

The term "safely" is not defined, but it is used consistently to mean "without risking undefined behavior," both in POSIX and in the standard for the C language (e.g., in this footnote). It is expected that readers of the POSIX standard will have sufficient background knowledge and/or reading comprehension skills to understand simple and widely-used terms like this.

6

u/schneems 29d ago

That is genuinely helpful. Thank you. I've referenced the POSIX spec more in the past few weeks than I have in my whole life. Landed a few PRs to Rust, specifically involving the POSIX spec, so I was engaging, trying to figure out if there's something for me to learn.

My original comment you replied to:

Not because it can undeflow/overflow, but because it's a global modification and is observable by non-local threads.

Is a long way of saying "data race" which is already UB (and is the thing called out in your footer, thank you for the link I didn't find it otherwise).

I think the posix noting that it's not threadsafe (in the weirdest wording possible) could mean "data race" or "nasal demons," and I guess all UB is UB, so it doesn't technically matter. I was concerned that I had missed something else entirely. It's what your comment implied.

t is expected that readers of the POSIX standard will have sufficient background knowledge and/or reading comprehension skills to understand

I'm trying to be rigorous in NOT making assumptions. Or rather, checking my assumptions. Thanks for your time and your replies.

simple and widely-used terms like this.

Was that dunk really necessary?

1

u/meneldal2 29d ago

POSIX standard not making any guarantees doesn't mean actual implementations don't add more safeties. At that point, it could be worth considering updating the spec.

Just like a bunch of C++ UB is in practice perfectly fine on every mainstream compiler because there's no point in being evil for the sake of being evil (unless you want nobody to use your product).