-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Eliminate the POSIX API layer #6600
Comments
a note on the Another point on vectored IO is that having the function take in a generic I would like to propose taking the idea of restructuring Make an |
@kprotty i fully support this! Nothing more to add |
I completely agree with this issue. Even Rust isn't a great example of how to do things, although we do fairly well at avoiding the most glaring flaw of Rust's OS abstractions at the moment: not supporting widechar APIs for Windows. I'm not sure we do this everywhere but I've seen Also, relying on the existence of a given compatibility layer on every platform, even if you restrict it to things you can currently shim over with reasonable precision, has proven to be a huge mistake for stabilized language ecosystems. For example, the introduction of WASI as a target has made the Rust standard library shim over a huge amount of details in a quite inefficient manner, because their APIs and API consumers are hard-dependent on absolute paths and/or being able to access files and directory paths without an existing directory handle. Zig mostly works with directory and file handles already, and supports iterating over WASI preopens instead of pretending paths like In essence, although plenty of things can be reasonably supported in a cross-platform manner, you cannot commit yourself to such a specific standard like POSIX and expect it to work flawlessly everywhere and fit every use case. |
I think the standard library has too much My half-baked hot take: a namespace like This could be layered. If I'm writing a package that allows Zig to target "Jayschwa OS", I could choose to satisfy the |
@jayschwa I agree that Sticking the implementation of everything in separate platform-specific modules is what makes the Rust standard library (and many Rust crates that copy the standard library's organization) horrific to navigate, and the more layers of this you have, the more you end up like our present day BYOS should be split up into more discretely implementable interfaces, but there should be no internal abstraction layer: |
I like the idea of a zig-style posix layer that unifies error codes, checks if arguments are correct, takes care of version prefixed/extended/safe apis. This is must have until zig's own higher level apis provide all possible interactions with the os. I think |
I've run into confusion regarding this as well, e.g. with (note that
It's unclear to me what behavior is 'correct,' and currently it feels like both |
If we go through with this proposal, are we making a decision on #1840 ? Writing platform code for Windows is pretty shaky right now, and if we're renaming it to posix or not is related. #5037 #4426 Edit: to clarify, #1840 'prefers' ntdll, but #4426 is on shakier ground, no proposal has been accepted. |
@squeek502 in my opinion all platform dependant behaviour in std.fs and other high level apis is a bug. For example in the case of realpath, on windows it should be emulated with few more syscalls, however if some behaviour is unemulatable then it should return error.Unsupported. |
I arrived here via the Zig English Telegram group. Here’s a related essay (found on Lobste.rs) about Go’s somewhat similar issues when compared with Rust: “Early Impressions of Go from a Rust Programmer” |
IMO only common behavior which is known to function fine on most operating systems should be exposed in a high-level interface. There shouldn't be a need for an When a user (of the OS library) requires an OS-specific API, they can get it from that OS-specific library. Fits with Zig zen of communicating intent precisely and preferring compile errors opposed to runtime crashes. |
I generally support this. That said, @LemonBoy Here are some questions I have about your vision. And note these are not arguments against it; my intent is to help move your project forward.
Also just to clarify - nothing in the std lib is required to go through Can we come up with some clear bullet points which act as guidelines for creating patches towards the goal of this issue? Maybe an example patch that incrementally moves the std lib towards this? (edit: also let's wait until 0.7.0 is tagged before starting to work on this, seems like a lot of breaking changes) |
The idea is for If a
The high-level abstractions build upon
As stated in the first point
Well this requires a bit of thought, the easieast solution would be to let high-level abstractions mentioned above add an extra arm in the platform-switching code. // openFile is fn (dir_handle: OSHandle, path: []const u8, options: OpenFileOptions) OSHandle
const openFile = switch (builtin.os.tag) {
.linux, .openbsd, .netbsd, ... => openFileUnix,
.windows => openFileWindows,
.freestanding => rootOs.openFile,
} But what if the OS you're targeting has no CC @IridescentRose as the PSP libc suffers from the lack of You can see that trying to accommodate every single use case means that we're painting ourselves in a corner and will have to target an unknown minimal set of features when designing the interface APIs. This idea proposed by @jayschwa is interesting on paper as it moves part of the complexity out of the stdlib and back into the BYOS implementer court.
Well the first step would be defining the set of high-level abstractions to build and define their behaviour and how to implement it on Linux/BSD/Windows/Wasi. const FileInfo = struct {
size: u64,
kind: enum { Directory, Regular, Pipe, ... },
mod_time: SomeCoolY38KTimestamp,
access_time: SomeCoolY38KTimestamp,
// permissions (as bools? as bitmap + accessors?)
// file mode
// a tagged union could be added to hold all the other platform-specific infos
};
fn getFileInfoByHandle(handle: OSHandle) FileInfoError!FileInfo {
// implement with statx on linux (or stat64 if not available)
// implement with stat on Darwin/BSD
// implement with GetFileInformationByHandleEx on WIndows
// implement with path_filestat_get on Wasi
} And voilá, we got a nice ergonomic (let's make extensive use of enums and getters) cross-platform abstraction that's even 2038-compliant! |
OK thanks for clarifying the vision! I can see how this would work going forward. |
@LemonBoy You may find it interesting to look at how SQLite does it, too. I believe it's similar in spirit to your proposal. (But it's also hot-pluggable with support for custom implementations and multiple implementations at once which is pretty neat) |
@LemonBoy Do you already have an idea of how the high-level abstraction will deal with OS-specific errors? Specifically, should there be error values that will only be returned on specific OSes (like the current |
Yep, the idea is to build a similar (but wider) cross-platform abstraction in the stdlib so that not every app/library has to invent their own.
If error unions had values we could just add a |
If I'm writing a new OS, I'm not even going to think about bugging Zig proper to upstream support for it until it's well and truly done (not that we would or should even consider it), so until then I'll be dependent on BYOOS for development. If BYOOS is optimised for POSIX, that's going to push me to make it at least POSIX-like. In my eyes, that's discouraging experimentation, and entrenching half-century-old ideas. I think the interface exposed by Apologies if this has already been said, it's 4am and my focus isn't great at the best of times. |
- Alphabetically sort things, where reasonable. - Document, that **only** non-portable posix things belong into posix.zig * If there is a portable abstraction, do not offer one in posix.zig * Reason: Prevent useless abstractions and needless strong coupling. - Move posix functions into posix.zig - Move wasi functions into wasi.zig Closes ziglang#6600.
- Alphabetically sort things, where reasonable. - Document, that **only** non-portable posix things belong into posix.zig * If there is a portable abstraction, do not offer one in posix.zig * Reason: Prevent useless abstractions and needless strong coupling. - Move posix functions into posix.zig - Move wasi functions into wasi.zig Closes ziglang#6600.
- Alphabetically sort things, where reasonable. - Document, that **only** non-portable posix things belong into posix.zig * If there is a portable abstraction, do not offer one in posix.zig * Reason: Prevent useless abstractions and needless strong coupling. - Move posix functions into posix.zig - Move wasi functions into wasi.zig Closes ziglang#6600.
- Alphabetically sort things, where reasonable. - Document, that **only** non-portable posix things belong into posix.zig * If there is a portable abstraction, do not offer one in posix.zig * Reason: Prevent useless abstractions and needless strong coupling. - Move posix-only functions into posix.zig, which have either incompatible or more extensive execution semantics than their counterparts and can be grouped into * File permission system * Process management * Memory management * IPC * Signaling Work on ziglang#6600.
- Alphabetically sort things, where reasonable. - Document, that **only** non-portable posix things belong into posix.zig * If there is a portable abstraction, do not offer one in posix.zig * Reason: Prevent useless abstractions and needless strong coupling. - Move posix-only functions into posix.zig, which have either incompatible or more extensive execution semantics than their counterparts and can be grouped into * File permission system * Process management * Memory management * IPC * Signaling Work on ziglang#6600.
- Alphabetically sort things, where reasonable. - Document, that **only** non-portable posix things belong into posix.zig * If there is a portable abstraction, do not offer one in posix.zig * Reason: Prevent useless abstractions and needless strong coupling. - Move posix-only functions into posix.zig, which have either incompatible or more extensive execution semantics than their counterparts and can be grouped into * File permission system * Process management * Memory management * IPC * Signaling Work on ziglang#6600.
As someone with a background in writing systems-level software (as in network daemons and such) in the traditional *nix/POSIX/C world, and who loves the idea of Zig-the-language as a C-the-language replacement for both porting old and developing new systems-level software, I'd like to offer an opinionated take on the current state of affairs, the reasonable stuff I've seen outlined above, and a few opinions of my own. Maybe this can at least restart the debate process towards an implementable outcome everyone can aim towards. Keep in mind I'm relatively-new to Zig itself and still finding my way around. If I've straight up misunderstood something, please let me know! Current state of affairs in
|
Further bits (which perhaps belong in a separate proposal?)Assuming the above seems reasonable, I do have some other thoughts beyond that which might merit either further debate here or perhaps a separate proposal:
|
Note very-related work ongoing in #19354 |
The
os
namespace (to be renamedposix
) is tackling the problem of providing a cross-platform abstraction over the operating system facilities from a, in my opinion, wrong angle. In this essay I will briefly explain why.The aim here is having a single level of abstraction that works well enough both for direct consumers (users of
std.os
namespace) or indirect consumers (eg. usingstd.fs
abstractions built onstd.os
) without leaking too many details about the underlying implementation. The current approach tries to clump everything under theposix
name, a name that carries a heavy baggage of do's and dont's that other platforms (mainly Windows) may not agree with.A few examples:
rename
doesn't follow the posix semantics on Windows, unless you have a very recent Windows 10 version andFILE_RENAME_FLAG_POSIX_SEMANTICS
is used.open
accepts a well-defined set of options that's not even shared across all the posix-compatible and for Windows this means we're pinky-swearing to faithfully translate all of them into something equivalent. What if there's no direct equivalent? If you promise posix compatibility I'd expect something akin tomingw
.stat
doesn't exist on Windows,GetFileAttributesEx
or equivalents are used and the resulting infos are chopped into a posixstat
structure. It's a clear case of square peg in a round hole as many fields are unix-specific.preadv
/pwritev
are not available on Windows and, at the moment at least, you get a nice compile error trying to use them.{write,read}Many
function, implemented usingp{write,read}v
orReadFileScatter
/WriteFileGather
.iovec
is a posix thing, I just want to write/read a[][]u8
!copy_file_range
is a Linux-only syscall with a fallback onpwrite
/pread
that in turn falls back onReadFile
/WriteFile
on Windows.os
as it can be safely implemented somewhere infs
, callingcopy_file_range
or other platform-specific APIs as needed.copy_file_range
is all about efficiency as it's done at the FS level, if the fallback path is triggered you get a disappointing read/write pair (not even a loop!)The point is that we should aim at breaking free from the posix rules and write our own, a full-blown posix compatibility layer is not something that belongs to the stdlib. I see Rust took this very same approach (I'm looking at the filsystem-related part), their API surface is small and comprises all the common bits required by the users (external ones and ones working on the stdlib). A small note lets the user know what platform-specific method is used, but that's it.
The gist of this proposal is:
os
becomes the home of all the cross-platform native interactions with the OS. No fallbacks, no posix names, no posix guarantees.os
, otherwise down the platform-specific namespace it goes.writeMany
implemented withWriteFileGather
andpwritev
copy_file_range
cannot be implemented as zero-copy on Windows! (On other posix-compatible systemssendfile
may be used, but the gains are really small unless you're copying a lot of data (I'm using it to copy whole files in std: Make file copy ops use zero-copy mechanisms #6516))Thanks for watching.
The text was updated successfully, but these errors were encountered: