Description
godot-rust historically (v0.8) used unsafe
for every interaction with the engine. While this was "on the safe side", it was very inconvenient and forced the user to clutter the code with unsafe
. v0.9 took a different approach with type-states: the user opts in to unsafety when converting Ref
to TRef
, and most (but not all) subsequent calls are safe. This improved a lot, but unfortunately there's still quite a bit of friction (#758 among others).
The more we looked into safety models, the more it became apparent that there's quite a bit of work left to do to come up with something consistent and uniformly applicable. Most notable, the following questions remain unanswered:
- What assertion does the user precisely make with
Ref::assume_safe()
? What about the safe ways to obtainTRef
(owner parameter)? Object lifeness is not enough. - Since calls to GDScript can execute arbitrary code, should every such invocation be marked
unsafe
? Or should we draw the boundary at Rust code, leaving the responsibility for correct GDScript implementation to the user? For the record, these are possible causes for UB when calling GDScript:- Destruction of objects in use, e.g. through
Object::free()
orReference::unreference()
. - Race conditions -- GDScript has full access to its own
Thread
class - Calls back to Rust via GDNative, i.e. execution of methods marked
unsafe
from safe code - Bugs in the C++ code of the engine (not worth protecting against)
- Destruction of objects in use, e.g. through
- How can we ensure that
unsafe
doesn't turn into an inflationary keyword that's used mindlessly whenever any Godot APIs are used? There is no point in being on the "academically correct side" if people start usingunsafe
like it means nothing. Ideally,unsafe
has the following purposes:- to limit the UB to few parts of Rust code, which can be the focus of auditing
- to make developers re-think if they really need
unsafe
whenever they write it - to make developers alert when they see it in existing code, and treat the surrounding logic with caution
A new model would ideally be consistent in the way that it's clear which methods are unsafe and which aren't, i.e. there should be a ruleset for inclusion/exclusion (with only few exceptions). It should also be pragmatic for above-mentioned reasons, a dogmatic "everything is unsafe" is not helping anyone. We have to embrace the fact that godot-rust's entire point is to deal with non-Rust code, whose safety is outside the library's control.
This issue hosts general discussion and this post serves as tracker for implementation tasks.
Large issues needing considerable design:
- Deref on the Unique typestate is unsound #635
- Add a special reference type that uses run-time reflection #714
- Simplify often-used and currently verbose APIs #758
More isolated, specific API shortcomings: