zig icon indicating copy to clipboard operation
zig copied to clipboard

Proposal: Write-only type qualifier

Open presentfactory opened this issue 1 year ago • 2 comments

Currently as part of Zig's type qualifiers const is offered to indicate read-only memory access through say a pointer. For whatever reason however C, C++ and other derivative languages often lack a (standard) generalization of this to write-only memory access, Zig being one such case.

The proposal is pretty simple, a new qualifier should be added to Zig to specify a write-only restriction on pointers so that something like the following can be written (using wonst to represent this write-only qualifier for lack of my ability to generate a better name):

var foo: i32 = undefined;
var bar: *wonst i32 = &foo;
var baz: *i32 = &foo;

bar.* = 5;
// bar.* += 5; // Error, implicitly reads through the pointer
bar = baz; // Fine, restricting access similar to *i32 -> *const i32
// baz = bar; // Error, cannot widen access to a read/write pointer

As for why someone might want such a feature there are a number of reasons:

  • Other languages such as C#, GLSL and HLSL to name a few have effectively a way to do this via out parameters (differing from say GLSL's inout for example which is closer to the behavior of a standard pointer and in which would be a constant pointer). Zig has no way to match such a construct other than by using a standard pointer with both read and write access.
  • Better self documenting code for out parameters on functions, similar to how const-correctness can help imply the intent of what is passed to a function (e.g. a constant pointer vs a standard pointer will indicate if the data will be touched and if the caller should be concerned about that). Write-only qualification helps indicate that the pointed value will only be written to so the caller can be confident in not initializing what they pass in knowing it will never be read from whereas this is unclear when standard pointers are used for this instead.
  • Better safety around special memory types. When working with MMIO registers or memory mapped across hardware (PCIe for example) often the act of reading from an address itself may have side effects, negative performance implications or may even be undefined behavior which ideally should be better guarded against. By qualifying a pointer as write-only it would prevent any accidental reads (if one wrote foo.* += 5; without thinking much about it for example) as well as giving the compiler more restrictions to avoid any "clever" optimizations it may do which may themselves involve potentially bugprone reads hidden to the user on the language level.
  • Potentially better compiler optimizations. I am not sure of any direct example of this but there are likely some niche cases where the compiler may be able to assume more about something if it knows it is only able to ever be written to rather than having both read/write access as it would right now.

Apologies if this was proposed already but I did not see it anywhere upon searching. The closest I saw was #1664 which has similar desires for low-level memory access. I think this proposal would cover that issue's use case as well via read-only and write-only qualified volatile pointers if I am understanding correctly while also allowing for benefits in typical day to day code (rather than it being something super specialized to something like MMIO only).

presentfactory avatar Mar 10 '23 07:03 presentfactory

IMO, within Zig, return values are almost always preferable to out-parameters, because the compiler can catch forgetting to return a value (you have to explicitly provide undefined), while it cannot catch forgetting to write to an out parameter (because it might be intentional - the option of this would be more clearly documented to the caller by returning an optional type). For multiple returns you can use a struct with named members.

That said, all other reasons (modelling other language's APIs, MMIO, and more compiler guarantees) seem sound to me. For keyword, since the use case won't be too common, I think simple writeonly is fine.

rohlem avatar Mar 10 '23 10:03 rohlem

IMO, within Zig, return values are almost always preferable to out-parameters

While I agree it's often preferable, I don't think we should disregard the use case. There are many instances in non-embedded and non-FFI code where you need out parameters, e.g.

  • initializing structs which need to be pinned,
  • optional out parameters & error diagnostics (like std.zig.CrossTarget.ParseOptions.Diagnostics),
  • basic runtime memory operations like std.mem.copy

Each these would benefit in using this modifier.

InKryption avatar Mar 10 '23 15:03 InKryption