Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/behavior-considered-undefined.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ r[undefined]
r[undefined.intro]
Rust code is incorrect if it exhibits any of the behaviors in the following list. This includes code within `unsafe` blocks and `unsafe` functions. `unsafe` only means that avoiding undefined behavior is on the programmer; it does not change anything about the fact that Rust programs must never cause undefined behavior.

r[undefined.behavior]
When a Rust program encounters undefined behavior, the program may perform arbitrary operations, including but not limited to jumping to arbitrary other code (even dead code) elsewhere in the program, performing arbitrary syscalls, or jumping into memory that does not hold valid machine code.
However, undefined behavior does not "time travel": if an observable operation (I/O or a volatile accesses) occurs before the point in the source execution order where undefined behavior was triggered, that observable operation is guaranteed to be executed before the program encounters undefined behavior.

@joshlf joshlf Sep 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about a simpler, smaller carve-out?

Suggested change
However, undefined behavior does not "time travel": if an observable operation (I/O or a volatile accesses) occurs before the point in the source execution order where undefined behavior was triggered, that observable operation is guaranteed to be executed before the program encounters undefined behavior.
However, undefined behavior does not "time travel": if an observable operation (I/O or a volatile accesses) occurs before the point in the source execution order where undefined behavior was triggered, that observable operation is guaranteed to be executed before the program encounters undefined behavior. If an observable operation which permits time travel is added in the future, it will be marked as such.

I don't feel super strongly about this – not enough to block FCP. IMO it'd be nice to at least nod at this so that careful authors understand the distinction. That said, I wasn't able to come up with a clear example of relying on "this generic observable operation prevents UB time travel".

View changes since the review

@RalfJung RalfJung Sep 2, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does "marked" mean?

The way I think this would go, if we ever wanted to support such operations, would be by adding a willreturn flag on asm that's similar to pure but allows I/O as long as the asm block can guarantee that that I/O will always return. A similar #[ffi_willreturn] attribute could be added for extern blocks. The documentation for those flags/attributes would then say "this can cause UB after the operation to time-travel to before the operation, causing whoever has to debug this code a loss of most of their hair".

Are you saying that we should promise that if we ever add such an attribute, that we will document it thoroughly to mention this caveat? That seems like an odd thing to say, I think the expectation is that we document every attribute we add. :)

@RalfJung RalfJung Sep 2, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
However, undefined behavior does not "time travel": if an observable operation (I/O or a volatile accesses) occurs before the point in the source execution order where undefined behavior was triggered, that observable operation is guaranteed to be executed before the program encounters undefined behavior.
However, undefined behavior does not "time travel": by default, if an observable operation (I/O or a volatile accesses) occurs before the point in the source execution order where undefined behavior was triggered, that observable operation is guaranteed to be executed before the program encounters undefined behavior.
> [!NOTE]
> There is currently no way to opt-out of that default. Such a mechanism may be added in the future, but that will not break any code not using that mechanism.

This is my attempt to steelman your proposal. I still would prefer not to add such a note, but this is the version I dislike the least. ;) I'd like to hear from the rest of the involved teams regarding whether we should add this or not.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RalfJung The only case I can think of where we might want this in the future would be much narrower than that. Essentially, if we add some specific operation which is allowed to time-travel, then UB interacting with that operation could time-travel to the extent that operation can time-travel.

@RalfJung RalfJung Sep 2, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know what you mean by "time-traveling operation". UB isn't an operation, so what we call "time-traveling UB" doesn't mean an operation is doing time-traveling. "no time-traveling UB" really just means "observable behavior is an optimization barrier for potentially-UB operations", or colloquially "if you print something before doing UB then you'll see the print".

We obviously allow e.g. two adjacent calls to read() to be reordered. That's not time-traveling, that's just a standard semantics-preserving transformation. And we don't allow observable behavior to be reordered; if you print first A then B you'll always see them in that order. There's not really any degrees of freedom here I can think of.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure – it makes it so that the default (ie, if an operation provides no documentation one way or another) is that UB can't time-travel over an operation, but saying "unless it documents otherwise" reserves the possibility that some operation might document it, which prevents the kind of generic reasoning I'm worried about.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But it would be wrong for an operation to document this. Docs can't change the opsem. We'd have to first add a new language feature that makes such operations exist. That's what I expressed in my variant of this.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And surely we will never have an operation that is allowed to have observable behavior before its invocation.

I can think of any number of such operations I'd like to see us have that would be perfectly reasonable. (You can have things observable before their apparent invocation, through compiler or processor reordering.) Let's not go on this tangent right now, it's not a blocker for the current proposal.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have written papers about operations that have this flavor so this is a topic I thought about quite a bit. I think this would break the ability for people to reason about what their program does; I don't think any sensible language can have operations like that. You get into causal loops and internal inconsistency basically immediately. Compilers and processors already do all sorts of reordering, none of them have this effect.

But anyway -- what do you propose for this PR then?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RalfJung For this PR I would propose that we not add any extra language disclaiming future things we might do, and close this subthread.


r[undefined.soundness]
It is the programmer's responsibility when writing `unsafe` code to ensure that any safe code interacting with the `unsafe` code cannot trigger these behaviors. `unsafe` code that satisfies this property for any safe client is called *sound*; if `unsafe` code can be misused by safe code to exhibit undefined behavior, it is *unsound*.

Expand Down
Loading