# Can the standard library shrink Option\<File\>?

**URL:** <https://internals.rust-lang.org/t/can-the-standard-library-shrink-option-file/12768>\
**Category:** libs\
**Created:** [July 22, 2020, 12:53am UTC](https://internals.rust-lang.org/t/can-the-standard-library-shrink-option-file/12768 "2020-07-22T00:53:25Z")\
**Posts on this page:** 18\
**Page:** 1

<div class="post-metadata">

**Author:** ![notriddle](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/notriddle/32/14082_2.png) [@notriddle](https://internals.rust-lang.org/u/notriddle)\
**Post date:** [July 22, 2020, 12:53am UTC](https://internals.rust-lang.org/t/can-the-standard-library-shrink-option-file/12768/1 "2020-07-22T00:53:25Z")

</div>

It seems like we should be able to add a niche optimization to the `FileDesc` type internal. With this optimization, an `Option<File>` will become the same size as a regular `File`.

```diff
diff --git a/src/libstd/sys/unix/fd.rs b/src/libstd/sys/unix/fd.rs
index c481ca8961f..090d9f7804a 100644
--- a/src/libstd/sys/unix/fd.rs
+++ b/src/libstd/sys/unix/fd.rs
@@ -10,6 +10,7 @@ use crate::sys_common::AsInner;
 use libc::{c_int, c_void, ssize_t};
 
 #[derive(Debug)]
+#[rustc_layout_scalar_valid_range_start(0)]
 pub struct FileDesc {
     fd: c_int,
 }
@@ -28,7 +29,8 @@ fn max_len() -> usize {
 
 impl FileDesc {
     pub fn new(fd: c_int) -> FileDesc {
- FileDesc { fd }
+ assert!(fd >= 0);
+ unsafe { FileDesc { fd } }
     }
 
     pub fn raw(&self) -> c_int {

```

## Can a valid file descriptor be negative?

On Linux, the answer is pretty obviously no. Linux file descriptors are stored in an array of structs that [has its capacity bounded](https://elixir.bootlin.com/linux/latest/source/fs/file.c#L27) to `INT_MAX`, so any negative `int` would either be considered nonsense (if treated as negative) or be higher than `INT_MAX` (if it was bit-reinterpreted as an unsigned value). Also, [notice how `dup2`](https://elixir.bootlin.com/linux/latest/source/fs/file.c#L905) returns a negative error code while also returning a positive file descriptor directly, as if they couldn't possibly overlap.

The Single UNIX Specification explicitly says that [`open`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html) can't return a negative file descriptor, and says in the page for [`dup2`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/dup2.html) that you should get EBADF if you try to claim a negative file descriptor.

Unfortunately, their description of [file descriptor allocation](https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_14) never explicitly says that the negative range is out of bounds, but `open` references this algorithm while simultaneously claiming that it never gives a negative result.

Also, [Wikipedia says it can't be negative](https://en.wikipedia.org/wiki/File_descriptor), but they don't give a source on that particular claim (urgh!).

## Does Rust's existing API allow it?

Here's what `FromRawFd` says about it.

> Constructs a new instance of `Self` from the given raw file descriptor.
> 
> This function **consumes ownership** of the specified file descriptor. The returned object will take responsibility for closing it when the object goes out of scope.
> 
> This function is also unsafe as the primitives currently returned have the contract that they are the sole owner of the file descriptor they are wrapping. Usage of this function could accidentally allow violating this contract which can cause memory unsafety in code that relies on it being true.

It certainly cannot "claim exclusive ownership" of a resource that does not exist, so presumably an invalid file descriptor must not be supplied to this function. Also, it's unsafe, so triggering memory problems should not be out of the question here.

But while it says that it "can cause memory unsafety," (1) that's not the same thing as being insta-UB (2) it doesn't actually say when, presumably because it might be platform-dependent.

Is this one of those cases where "it's not worth the possibility that someone, somewhere, might be relying on this," even though it's not clearly allowed here? Or am I just twisting the language to try to support my pet project, and stuffing a number that isn't a file descriptor into a File is fine as long as you `forget` it before it tries to call `close`?

---

<div class="post-metadata">

**Author:** ![chrisd](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/chrisd/32/7232_2.png) [@chrisd](https://internals.rust-lang.org/u/chrisd)\
**Post date:** [July 22, 2020, 1:43am UTC](https://internals.rust-lang.org/t/can-the-standard-library-shrink-option-file/12768/2 "2020-07-22T01:43:33Z")

</div>

If `Option<File>` and `File` are made to have the same size on nix then shouldn't Rust commit to making that a part of the API's contract for all platforms (current and future)? People are very good at relying on these kinds of `Option` optimizations so I'd worry a nix only one might lead to issues down the road.

---

<div class="post-metadata">

**Author:** ![notriddle](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/notriddle/32/14082_2.png) [@notriddle](https://internals.rust-lang.org/u/notriddle)\
**Post date:** [July 22, 2020, 2:15am UTC](https://internals.rust-lang.org/t/can-the-standard-library-shrink-option-file/12768/3 "2020-07-22T02:15:35Z")

</div>

I don't think so.

For one thing, it's not obvious that it would be possible on every platform that Rust might target. What happens on a target where a file handle is allowed to cover every possible value?

For another, I'm not sure it would actually help. The primary case where you'd want this is FFI, and that will ultimately be platform-specific anyway, because it's dependent on what the File actually contains.

---

<div class="post-metadata">

**Author:** ![josh](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/josh/32/5934_2.png) [@josh](https://internals.rust-lang.org/u/josh)\
**Post date:** [July 22, 2020, 3:40am UTC](https://internals.rust-lang.org/t/can-the-standard-library-shrink-option-file/12768/4 "2020-07-22T03:40:12Z")

</div>

I don't think we can safely say that _all_ negative values are invalid file descriptors, but we don't need to. We _can_ say that values from -1 to -4095 can never be valid file descriptors, because if a system call returned those, they would be interpreted as a negative errno value rather than a valid return.

(By the same argument, Linux uses `-100` as the value for `AT_FDCWD`, the value passed to the `*at` variants of system to indicate the current directory.)

`File::FromRawFd` could assert that the file descriptor is not -1 to -4095.

Can we declare `-1` to `-4095` a niche?

---

<div class="post-metadata">

**Author:** ![josh](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/josh/32/5934_2.png) [@josh](https://internals.rust-lang.org/u/josh)\
**Post date:** [July 22, 2020, 3:47am UTC](https://internals.rust-lang.org/t/can-the-standard-library-shrink-option-file/12768/5 "2020-07-22T03:47:34Z")

</div>

A similar niche would also be useful inside the Linux kernel, which has a concept of an error/pointer combination type: in addition to NULL, 4095 other values are reserved and cannot be valid pointers, so functions can return a value that's effectively `Result<SomeType *, errno>` (but without the type safety). Could we make it possible to declare this explicitly, so that C and Rust could interoperably represent the concept of a pointer-sized `Result` containing a pointer or errno, and Rust could do so with type safety and the `?` operator?

---

<div class="post-metadata">

**Author:** ![CAD97](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/cad97/32/3460_2.png) [@CAD97](https://internals.rust-lang.org/u/CAD97)\
**Post date:** [July 22, 2020, 4:26am UTC](https://internals.rust-lang.org/t/can-the-standard-library-shrink-option-file/12768/6 "2020-07-22T04:26:43Z")

</div>

> [@josh](#):
>
> Can we declare `-1` to `-4095` a niche?

IIRC, niche optimizations support an arbitrary (but single) range of niche (invalid) values to stuff data into. However, rustc is only capable at the moment of specifying `#[rustc_scalar_valid_range_start]`, which makes any value less than the argument an invalid value acceptable for niching.

> [@josh](#):
>
> `Result<SomeType *, errno>`

rustc isn't possible of even [simplified versions of that niche optimization](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=733447c9ea51e7784eee802d84348b1d) yet, but nothing prevents it from being able to do so in the future. The difficulty is that you have to be able to take a reference to any contained value, so it's not enough for `errno` to fit in the niche, it also has to actually physically _be_ entirely contained in the niche.

This can, of course, be imitated by a trio of types which `#[repr(transparent)]` and do the niche packing/checking/transmuting manually (and with `impl Try`, you can get `?` support as well).

---

<div class="post-metadata">

**Author:** ![josh](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/josh/32/5934_2.png) [@josh](https://internals.rust-lang.org/u/josh)\
**Post date:** [July 22, 2020, 6:17am UTC](https://internals.rust-lang.org/t/can-the-standard-library-shrink-option-file/12768/7 "2020-07-22T06:17:17Z")

</div>

> > [@josh](#):
> >
> > Can we declare `-1` to `-4095` a niche?
> 
> IIRC, niche optimizations support an arbitrary (but single) range of niche (invalid) values to stuff data into. However, rustc is only capable at the moment of specifying `#[rustc_scalar_valid_range_start]`, which makes any value less than the argument an invalid value acceptable for niching.

Does the internal architecture of rustc support setting an arbitrary range, and we just need an attribute to convey that range to rustc? Because if so, that seems like a fairly simple language extension to add.

> > [@josh](#):
> >
> > `Result<SomeType *, errno>`
> 
> rustc isn't possible of even [simplified versions of that niche optimization](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=733447c9ea51e7784eee802d84348b1d) yet, but nothing prevents it from being able to do so in the future. The difficulty is that you have to be able to take a reference to any contained value, so it's not enough for `errno` to fit in the niche, it also has to actually physically _be_ entirely contained in the niche.

Technically it _could_ be contained within the niche, insofar as the in-memory representation would be the same. If we guarantee that a `NegativeErrno` has no more than 4095 different values, and that a valid pointer can never have one of those values, then the bytes of a `NegativeErrno` value would be entirely contained within the bytes of the niched pointer, and a reference would work.

> This can, of course, be imitated by a trio of types which `#[repr(transparent)]` and do the niche packing/checking/transmuting manually (and with `impl Try`, you can get `?` support as well).

True. That type could also be a generic that supports arbitrary pointers or references, so that the pointer remains type-safe.

It would be nice, though, if we could arrange for that type to actually _be_ `Result`. Lots of things know how to work with a `Result` (or with functions that return a `Result`), and `Result` has so many nice helpers that we wouldn't want to duplicate.

---

<div class="post-metadata">

**Author:** ![eddyb](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/eddyb/32/8411_2.png) [@eddyb](https://internals.rust-lang.org/u/eddyb)\
**Post date:** [July 22, 2020, 2:34pm UTC](https://internals.rust-lang.org/t/can-the-standard-library-shrink-option-file/12768/8 "2020-07-22T14:34:01Z")

</div>

> [@CAD97](#):
>
> However, rustc is only capable at the moment of specifying `#[rustc_scalar_valid_range_start]` , which makes any value less than the argument an invalid value acceptable for niching.

This isn't true though? [There's an `_end` just like there's a `_start`](https://github.com/rust-lang/rust/blob/39d5a61f2e4e237123837f5162cc275c2fd7e625/src/librustc_middle/ty/context.rs#L1058-L1059).

Inside `rustc` itself we use the last 256 or so values of newtyped indices, as a niche.

cc @oli-obk

---

<div class="post-metadata">

**Author:** ![CAD97](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/cad97/32/3460_2.png) [@CAD97](https://internals.rust-lang.org/u/CAD97)\
**Post date:** [July 22, 2020, 2:43pm UTC](https://internals.rust-lang.org/t/can-the-standard-library-shrink-option-file/12768/9 "2020-07-22T14:43:51Z")

</div>

Things have been improved since I last brushed by it, then. If I'm not mistaken, it's still not possible to specify an internal hole as a niche yet, but obviously I don't know for sure.

---

<div class="post-metadata">

**Author:** ![jstarks](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/jstarks/32/6296_2.png) [@jstarks](https://internals.rust-lang.org/u/jstarks)\
**Post date:** [July 22, 2020, 2:47pm UTC](https://internals.rust-lang.org/t/can-the-standard-library-shrink-option-file/12768/10 "2020-07-22T14:47:37Z")

</div>

Surely that’s too conservative—it’s standard practice in user mode to check if the return value of open, dup, etc. is negative, not to compare it against -1. Even if the Linux kernel wanted to support negative fds, it couldn’t return them to user mode without severe breakage.

---

<div class="post-metadata">

**Author:** ![josh](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/josh/32/5934_2.png) [@josh](https://internals.rust-lang.org/u/josh)\
**Post date:** [July 22, 2020, 6:22pm UTC](https://internals.rust-lang.org/t/can-the-standard-library-shrink-option-file/12768/11 "2020-07-22T18:22:11Z")

</div>

The system call interface and the library interface are two different things. The system call interface only uses `-1` to `-4095` as errors, and those get translated to `errno` values in userspace. The corresponding library calls return `-1` on errors, and technically userspace _should_ just check for that.

(File descriptors _should_ have always been unsigned values with a small reserved range, rather than signed values, but that would be hard to change now.)

Returning a negative file descriptor from something like `open` _would_ break many bits of userspace, but that's unlikely to ever happen anyway, as `open` returns the lowest available file descriptor.

However, that doesn't mean that _other_ calls couldn't potentially work with so-large-they're-negative file descriptors. I've seen and participated in discussions that proposed letting userspace explicitly use negative file descriptors for some designated purpose.

> [@eddyb](#):
>
> > [@CAD97](#):
> >
> > However, rustc is only capable at the moment of specifying `#[rustc_scalar_valid_range_start]` , which makes any value less than the argument an invalid value acceptable for niching.
> 
> This isn't true though? [There's an `_end` just like there's a `_start`](https://github.com/rust-lang/rust/blob/39d5a61f2e4e237123837f5162cc275c2fd7e625/src/librustc_middle/ty/context.rs#L1058-L1059).

It sounds like we could potentially specify the range from -4095 to -1 as a niche for `File` values, and that should solve the issue of `Option<File>` and other simple niche usage.

---

<div class="post-metadata">

**Author:** ![felix.s](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/felix.s/32/8073_2.png) [@felix.s](https://internals.rust-lang.org/u/felix.s)\
**Post date:** [July 22, 2020, 8:49pm UTC](https://internals.rust-lang.org/t/can-the-standard-library-shrink-option-file/12768/12 "2020-07-22T20:49:29Z")

</div>

> [@josh](#):
>
> in addition to NULL, 4095 other values are reserved and cannot be valid pointers

Somewhat drifting off-topic, but do you know how it happens to interact with a lowered `mmap_min_addr`?

---

<div class="post-metadata">

**Author:** ![josh](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/josh/32/5934_2.png) [@josh](https://internals.rust-lang.org/u/josh)\
**Post date:** [July 22, 2020, 9:07pm UTC](https://internals.rust-lang.org/t/can-the-standard-library-shrink-option-file/12768/13 "2020-07-22T21:07:05Z")

</div>

> [@felix.s](#):
>
> Somewhat drifting off-topic, but do you know how it happens to interact with a lowered `mmap_min_addr` ?

Unrelated as far as I know. That only controls valid _userspace_ pointers, not valid _kernel_ pointers. If `-1` or `-4095` were a pointer, it'd be at the top of memory, not the bottom, and the top of memory belongs to the kernel.

---

<div class="post-metadata">

**Author:** ![jstarks](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/jstarks/32/6296_2.png) [@jstarks](https://internals.rust-lang.org/u/jstarks)\
**Post date:** [July 22, 2020, 11:48pm UTC](https://internals.rust-lang.org/t/can-the-standard-library-shrink-option-file/12768/14 "2020-07-22T23:48:51Z")

</div>

> [@josh](#):
>
> The system call interface and the library interface are two different things. The system call interface only uses `-1` to `-4095` as errors, and those get translated to `errno` values in userspace. The corresponding library calls return `-1` on errors, and technically userspace _should_ just check for that.

Yes, but (generally) only the library interface matters for Rust's implementation of std. The system call interface is the business of libc. As a practical difference, it seems that we would want to find a niche that applies across all Unix-style targets, not just Linux.

> [@josh](#):
>
> However, that doesn't mean that _other_ calls couldn't potentially work with so-large-they're-negative file descriptors. I've seen and participated in discussions that proposed letting userspace explicitly use negative file descriptors for some designated purpose.

Sure, Windows has this trick: user-mode handles are all positive (when interpreted as a signed integer), but there are so-called pseudo handles implemented as well-known negative values that are recognized by the kernel to mean "current thread", "current process", or similar. Windows doesn't have any pseudo handles that apply to files today, but that could change.

But in such cases, presumably the fd would not be one that you'd pass to `close`. It wouldn't be one that would reasonably be owned by `File`.

---

<div class="post-metadata">

**Author:** ![josh](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/josh/32/5934_2.png) [@josh](https://internals.rust-lang.org/u/josh)\
**Post date:** [July 23, 2020, 4:01am UTC](https://internals.rust-lang.org/t/can-the-standard-library-shrink-option-file/12768/15 "2020-07-23T04:01:31Z")

</div>

> [@jstarks](#):
>
> Yes, but (generally) only the library interface matters for Rust's implementation of std. The system call interface is the business of libc. As a practical difference, it seems that we would want to find a niche that applies across all Unix-style targets, not just Linux.

Well, if you want a niche that works _everywhere_, I'd suggest just making `-1` the niche, which is enough to make `Option` work.

The remainder of the discussion was more about an optimization I'd like to see for the use of Rust _inside_ the Linux kernel.

> [@jstarks](#):
>
> But in such cases, presumably the fd would not be one that you'd pass to `close` . It wouldn't be one that would reasonably be owned by `File` .

In the case I was talking about, they would have been real file descriptors that would need opening and closing.

---

<div class="post-metadata">

**Author:** ![notriddle](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/notriddle/32/14082_2.png) [@notriddle](https://internals.rust-lang.org/u/notriddle)\
**Post date:** [July 23, 2020, 10:17pm UTC](https://internals.rust-lang.org/t/can-the-standard-library-shrink-option-file/12768/16 "2020-07-23T22:17:08Z")

</div>

> [@josh](#):
>
> Well, if you want a niche that works _everywhere_ , I'd suggest just making `-1` the niche, which is enough to make `Option` work.

Okay, sure. Let's do that, then.

> <https://github.com/rust-lang/rust/pull/74699>
>
> Based on discussion from \<https://internals.rust-lang.org/t/can-the-standard-lib…rary-shrink-option-file/12768\>, the file descriptor \`-1\` is chosen based on the POSIX API designs that use it as a sentinel to report errors. A bigger niche could've been chosen, particularly on Linux, but would not necessarily be portable.
> 
> This PR also adds a test case to ensure that the -1 niche (which is kind of hacky and has no obvious test case) works correctly. It requires the "upper" bound, which is actually -1, to be expressed in two's complement.

---

<div class="post-metadata">

**Author:** ![josh](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/josh/32/5934_2.png) [@josh](https://internals.rust-lang.org/u/josh)\
**Post date:** [July 23, 2020, 10:40pm UTC](https://internals.rust-lang.org/t/can-the-standard-library-shrink-option-file/12768/17 "2020-07-23T22:40:01Z")

</div>

Thanks! I posted a few comments, but overall that looks great.

---

<div class="post-metadata">

**Author:** ![notriddle](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/notriddle/32/14082_2.png) [@notriddle](https://internals.rust-lang.org/u/notriddle)\
**Post date:** [October 21, 2020, 10:40pm UTC](https://internals.rust-lang.org/t/can-the-standard-library-shrink-option-file/12768/18 "2020-10-21T22:40:07Z")

</div>

Closed on October 21, 2020, 10:40pm UTC

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.
