Skip to content
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

Figure out the future of nix #190

Open
carllerche opened this issue Sep 15, 2015 · 19 comments
Open

Figure out the future of nix #190

carllerche opened this issue Sep 15, 2015 · 19 comments
Milestone

Comments

@carllerche
Copy link
Contributor

tl;dr Should nix be split up into multiple crates named nix-* and what should the API style be?

The situation

Nix originated as a posix binding dumping ground for various crates that I had been working on. I tended to need the same bindings over and over again, so I just moved them into Nix. I thought this would be useful for other people, so I accepted PRs for various system APIs as they have been submitted.

Everything has been fine so far, but a few issues have arisen.

As the API coverage has increased, whenever a breaking change is made, the semver must be incremented. So, if the ioctl module changes an API as it figures out the best way to expose functionality, the version must be bumped and downstream crates must change their dependencies. For example, Mio depends on nix, but not on any ioctl features, so bumping the dependency is pretty annoying.

Secondly, the API style has been somewhat ad hoc, especially with more complicated system APIs like fcntl.

Crate structure

There is value in having a single place (nix) to find system APIs that have a coherent API style and a single documentation site. I think that each specific API silo, like sockets, ioctl, mqueue, signals, etc... could be in their own crate named nix_*. Common features like errors, NixPath, etc... would live in nix_core. The nix crate would be a facade depending on all the sub crates with liberal dependencies (probably "*") which would allow the consumer to optionally be able to pin each sub crate to whatever.

Libraries like Mio could then depend only on the features that are needed.

Also, it would allow delegating responsibility better. I, for one, know nothing about ioctl. I would like to stop being the blocker.

API style

This is still something that I haven't figured out. I do know that the goal of nix is *comprehensive, safe, zero cost bindings to system APIs on nix OSes.

So, what does that mean for nix? As of now, there are currently a variety of styles. For example, most APIs are 1-1 bindings to the equivalent OS api. This leads to APIs like fcntl which uses a convoluted FcntlArg strategy. ioctl on the other hand uses a (fairly impressive) macro...

The question is, would it be better to expand beyond the 1-1 rule. For example, in the fnctl case, the API would most likely be much cleaner and more "rusty" if each "fcntl arg" was moved to an individual function. There could, for example, be a public fcntl mod with a dupfd function. Using it would be:

let res = fcntl::dup_fd(fd);

Then, should it go further? Should there be an Fd type that wraps std::os::unix::RawFd and implement functions like dup directly on that type?

These are all open questions that I pose to the users of nix.

cc @posborne @cmr @geofft @utkarshkukreti @MarkusJais and whoever else :)

@aturon
Copy link

aturon commented Sep 15, 2015

cc @alexcrichton (possibly relevant for liblibc plans?)

@joekain
Copy link
Contributor

joekain commented Sep 15, 2015

I’m not sure what the best approach is here but here are few thoughts I had:

Also, it would allow delegating responsibility better. I, for one, know nothing about ioctl. I would like to stop being the blocker.

This is possible within a single crate if you wanted to give others ability to merge pull requests. Multiple crates would give finer control e.g. someone can have permission to merge ioctl changes but not fcntl changes.

I do know that the goal of nix is comprehensive, safe, zero cost bindings to system APIs on *nix OSes.

Awesome.

As of now, there are currently a variety of styles

This is something to discuss in terms of developing nix but I think single crate or multiple crates this still imposes multiple styles on users of the crate.

@carllerche
Copy link
Contributor Author

@joekain My proposal would be to keep all the code in the same git repository, but to release individual crates to crates.io. This would only be to allow the versions to progress independently. There should be a consistent style across all the individual crates.

@carllerche
Copy link
Contributor Author

@alexcrichton also mentioned that he had a goal of generating all the constants / unsafe FFI definitions needed for nix in the the libc crate, which would allow nix to purge all the unsafe / const bindings in favor of focusing entirely on the Rust API.

@alexcrichton
Copy link
Contributor

Ah and to clarify, I'm not sure we'll be generating the libc crate, but we're going to start verifying it much more rigorously, so it should be much easier to add to but will still require additions.

@posborne
Copy link
Member

Thanks for bringing this up @carllerche.

I am in favor of your proposal to retain a single git repository for the "official" nix libraries in order to provide consistency but breaking out subsystems into discrete crates that are largely independent (with the exception of nix_core).

The level of abstraction question is one that I have been thinking about more as changes have been made with the ioctl system. With that change, I feel that it may have been a better (in hindsight) to expose the functionality in layers:

I think all layers up to 3 could have a good spot within nix, especially with discrete crates but that designing in terms of layers and exposing each layer to users probably makes sense in most cases as well. The higher level the abstraction, the more likely it is that a use case might be prevented.

I believe Nix should aim to expose the highest level (most safe) API that presents an unlimited (a user can do everything needed), zero-cost abstraction as a start. To benefit users, nix crates may also expose APIs that sacrifice flexibility in favor of safety for select use cases.

@droundy
Copy link
Contributor

droundy commented Nov 30, 2015

My vote (as a user who is only starting to use nix) would be to keep nix as a monolithic crate, and just let users bump their version dependencies. They shouldn't be forced to bump the version that frequently, right? i.e. it should be safe for them to continue using an older version for some time.

I also think that developing a rusty (and complete) API would be ideal. e.g. the ptrace signature is currently unsafe (although not marked so?!), and could productively be replaced with multiple helper functions that are actually safe, since the types of the later arguments depend on the flag.

@kamalmarhubi
Copy link
Member

FYI I filed #214 about exporting all functions / types at the crate root as is now done in the libc crate. It's probably relevant to this discussion.

@carllerche
Copy link
Contributor Author

@droundy the main driver around splitting up the crate is that a breaking change in a less thought out part of the API isn't a breaking change in other parts...

@kamalmarhubi Yep, thanks for filing it.

In general, this issue is on hold until libc stabilizes a bit and pulls in the required symbols.

@MarkusJais
Copy link

I think the most important thing is to have a consistent style for APIs.

Putting the low-level bindings in libc is a good idea and then nix could just wrap them so no unsafe stuff is necessary for the user of nix. And we could add convenience functions on top of the low-level bindings (e.g. the mq_set_nonblock/mq_remove_nonblock I've added to mqueue.rs) that makes using the APIs easier (but avoiding any serious overhead!)

I am happy to remove all the low-level mqueue stuff once it is in libc.

@kamalmarhubi
Copy link
Member

I like the idea of moving as much of the current ffi module stuff to libc as possible. The main things attracting me to nix are:

  • no unsafe necessary in user code
  • Rustified error handling

I don't have enough experience in any of the areas where nix has fancier bindings, so I can't comment on that at all. I can say I do like things mapping directly to man pages for what I'm trying to do at the moment.

@fiveop
Copy link
Contributor

fiveop commented Jan 20, 2016

I mostly agree with @posborne's four layers, although I do not quite see the need for the distinction between two and three (or rather the existance of level two). That leaves me with three levels:

  1. libc: essentially 1:1 ffi, raw and unsafe.
  2. nix: close to zero cost abstractions that provide an idomatic API using Result, Optional, structs with methods, bitflags, etc., 100% functionality available to the user, no unsafe API
  3. See Level 4 of @posborne.

In commit 142045f in my dev branch, I changed the signals module toward that goal.

  • I replaced almost all constants with libc constants.
  • I replaced all ffi function declarations with their libc equivalents.
  • I replaced the low level structs with their corresponding libc structs. This is the first breaking change, because I could not use the word "equivalents" in the previous sentence.
  • I removed the export of the raw low level structs. This is also a breaking change. I would rather support the idea of exporting the full libc under nix::libc. Currently, there is no way to retrieve the raw structs from their wrapping counterparts. I would not mind to include this using AsRef implementations.

In addition to these changes I would also liked to have removed the type alias SigNum, but then I would have had to touch the wait module as well. I do not see much value in types aliases for primitive types from libc.

The commit would not bring signals all the way to where it should be according to 2. above. We do not have 100% functionality, e.g. the results do not distinguish error types. There is almost no documentation.

@kamalmarhubi
Copy link
Member

I don't have a proper understanding of how multiple versions of a crate play together. According to #226, tying nix's types to libc could result in downstream users having weird effects due to multiple libc versions being around. @fiveop do you have any insight, since this seems to go against what you're suggesting / working towards with signals.

@kamalmarhubi
Copy link
Member

In general, this issue is on hold until libc stabilizes a bit and pulls in the required symbols.

We can start this discussion up again, as I think libc is pretty stable now. There have been 8 releases in the 0.2 series: https://crates.io/crates/libc/versions

Goals of nix

@MarkusJais said:

Putting the low-level bindings in libc is a good idea and then nix could just wrap them so no unsafe stuff is necessary for the user of nix.

I like this as a lowest level aim for nix. Use system APIs without unsafe. I filed #264 a while ago to track removing our own definitions, migrating to libc as much as possible.

@fiveop said:

  1. libc: essentially 1:1 ffi, raw and unsafe.
  2. nix: close to zero cost abstractions that provide an idomatic API using Result, Optional, structs with methods, bitflags, etc., 100% functionality available to the user, no unsafe API
  3. See Level 4 of @posborne: Safe APIs for specific drivers based on top of layer 2/3. Examples of that would be crates like https://github.com/posborne/rust-spidev and https://github.com/posborne/rust-i2cdev

(I copied in @posborne's level 4.)

I agree with @posborne that "all layers up to 3 could have a good spot within nix", ie that levels 1-2 in @fiveop's summary work well in Nix.

Crate / repo structure

I'm in favour of single repo / multiple crates. I think this pattern has been used pretty well in a few crates in the ecosystem.

Single repo keeps the development cohesive and in one place. Multiple crates gets rid of unnecessary version coupling. There are APIs where the best way to handle them in nix is pretty clear. We've got the unistd basics down pretty well. But the ioctl, fcntl, and sockopt areas seem a bit more in flux, and are more likely to have breaking changes. It would be annoying to me as a downstream user to have to bring in a version with potential incompatibilities just because I want a fix to one of the more solid areas.

kamalmarhubi referenced this issue in abbradar/nix-rust Feb 19, 2016
@abbradar
Copy link
Contributor

A comment regarding usage of libc types where possible (continuing the discussion) -- I think that we should go this way as much as possible, and don't hesitate to wrap them with our own for the end user if it seems a good idea. For example, fd_set from libc is pretty inconvenient (unsafe FD_* functions), but we can wrap it in our own FdSet and make it much more idiomatic.

@fiveop
Copy link
Contributor

fiveop commented Feb 22, 2016

For the record:

I wrote

  1. nix: [i...] no unsafe API

I am now certain that there are some system calls that nix cannot provide a safe interface for. For example, we as a library can never ensure that a handler installed using the function sigaction does not make unsafe calls (on the operating system level). No library, that does not also provide the installable handler functions, can provide a safe interface.

@kamalmarhubi
Copy link
Member

@fiveop

handler installed using the function sigaction does not make unsafe calls (on the operating system level)

Could you elaborate a little, or give an example? I haven't used the signal stuff, but this sounds important to understand.

@kamalmarhubi
Copy link
Member

@abbradar

I think that we should go this way as much as possible, and don't hesitate to wrap them with our own for the end user if it seems a good idea

Yeah I think this makes sense. It fits in with @fiveop's 2. above, which includes "structs with methods" under providing an idiomatic API.

@Kixunil
Copy link
Contributor

Kixunil commented Jan 31, 2021

One more thing I'd love to see is reorganizing modules/subcrates more similarly to std. I get the appeal of mirroring libc but after a few years of not working with it I have trouble to remember in which module particular function/type is defined.

Having both ways, one being facade of the other is also fine.

Maybe also eventually getting some well-tested things to std?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests