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

BorrowedFd::to_owned() gives you another BorrowedFd #88564

Closed
ijackson opened this issue Sep 1, 2021 · 5 comments
Closed

BorrowedFd::to_owned() gives you another BorrowedFd #88564

ijackson opened this issue Sep 1, 2021 · 5 comments
Labels
A-io Area: `std::io`, `std::fs`, `std::net` and `std::path` C-bug Category: This is a bug. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.

Comments

@ijackson
Copy link
Contributor

ijackson commented Sep 1, 2021

Firstly, I should say that I really like the basic ideas in RFC3128 (#87074) . I'm reporting a wrinkle which may or may not be soluble.

This program:

#![feature(io_safety)]
use std::os::unix::prelude::*;

fn main() -> std::io::Result<()> {
    let file = std::fs::File::open("/dev/null")?;
    let borrowed = file.as_fd();
    let owned = borrowed.to_owned();
    eprintln!("file={:?}\nborrd={:?}\nowned={:?}", &file, &borrowed, &owned);
    Ok(())
}

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=d51ddfa5a390e4d1b8f8bf81af8b0d73

Prints:

file=File { fd: 3, path: "/dev/null", read: true, write: false }
borrd=BorrowedFd { fd: 3 }
owned=BorrowedFd { fd: 3 }

A naive reader might have thought it would print:

file=File { fd: 3, path: "/dev/null", read: true, write: false }
borrd=BorrowedFd { fd: 3 }
owned=OwnedFd { fd: 4 }

But of course converting a borrowed to an owned fd is fallible. And there is no TryToOwned. This no-op to_owned() exists because BorrowedFd is Copy and therefore Clone and eveyrhing Clone has a no-op ToOwned.

The ideal fix from a type system and traits point of view (other than going back in time and abolishing the blanket ToOwned for Clone) would be for BorrowedFd to somehow be a reference type. But it would have to actually be represented as an integer. pub struct BorrowableFdValue { x:() } and transmuting c_int via usize to &BorrowableFdValue and back would solve this but that is really quite stomach-churning (and the result can't be used in ffi the way the existing BorrowedFd can).

Maybe the answer is to simply document this quirk. We should probably provide impl TryFrom<OwnedFd> from BorrowedFd at the very least, and of course OwnedFd::try_clone().

@ijackson ijackson added the C-bug Category: This is a bug. label Sep 1, 2021
@sunfishcode
Copy link
Member

Thanks for the report!

Another option would be to remove the Copy and Clone impl for BorrowedFd. Somewhat surprisingly, they turn out to be infrequently used from the use cases I've seen so far, because the common pattern is to do as_fd() or FFI to obtain a BorrowedFd, use the BorrowedFd without copying it because it has a limited scope and there usually isn't a need to have multiple copies of the same handle within that scope, and then drop it. And one could still call as_fd() on a BorrowedFd to obtain a copy of it if desired. Thoughts?

Is there a need for impl TryFrom<OwnedFd> for BorrowedFd, given that we can just use the infallible as_fd() to obtain a BorrowedFd from an OwnedFd?

The reason I didn't add OwnedFd::try_clone was that the semantics of what's shared and what's independent after a dup depend on the dynamic type of the resource they refer to, so the use cases for a generic try_clone aren't clear to me. Could you say more about how you'd use this?

@sunfishcode sunfishcode added A-io Area: `std::io`, `std::fs`, `std::net` and `std::path` T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. labels Sep 1, 2021
@ijackson
Copy link
Contributor Author

ijackson commented Sep 1, 2021

Another option would be to remove the Copy and Clone impl for BorrowedFd. ...Thoughts?

Hrm. Interesting. I haven't worked with this API much, so I don't feel I qualified to have an opinion. It does feel a bit odd, but I think it's going to be odd either way.

Is there a need for impl TryFrom<OwnedFd> for BorrowedFd, given that we can just use the infallible as_fd() to obtain a BorrowedFd from an OwnedFd?

Err. I miswrote. I meant impl TryFrom<BorrowedFd> for OwnedFd. Ie, the try_clone(), only starting from a BorrowedFd.

The reason I didn't add OwnedFd::try_clone was that the semantics of what's shared and what's independent after a dup depend on the dynamic type of the resource they refer to, so the use cases for a generic try_clone aren't clear to me. Could you say more about how you'd use this?

I think anyone who is working with fds knows (ought to know) what the implications are. Obviously we should document that try_clone is just dup, but there isn't anything else it could be.

As for the "how you'd use this", I was just doing some work in std::process::Command: #88561. The internals there could make good use of BorrowedFd. I felt dirty open-coding a call to libc::dup :-).

Also, I think there are a number of places where APIs consumes an fd that could very helpfully take F: TryInto<OwnedFd> or F: AsFd. For example, at some point perhaps impl<F> Into<process::Stdio> for F where F: AsFd, whose implementation needs TryInto.

sunfishcode added a commit to sunfishcode/rust that referenced this issue Sep 9, 2021
As suggested in rust-lang#88564. This adds a `try_clone()` to `OwnedFd` by
refactoring the code out of the existing `File`/`Socket` code.
@sunfishcode
Copy link
Member

sunfishcode commented Sep 9, 2021

#88794 adds try_clone() to OwnedFd.

On removing BorrowFd's Copy+Clone: This went smoothly in a few codebases I tried, which suggests the change is feasible. Before I submit a PR though, I want to figure out BorrowedFd::try_clone first.

Should we add BorrowedFd::try_clone? It is safe. However, it's somewhat surprising; a typical "borrow" means access to the resource is statically scoped, but a BorrowedFd::try_clone would mean that access to the resource could outlive the static scope. Most uses of BorrowedFd don't need to dup it. I have two ideas so far:

  • Don't add BorrowedFd::try_clone, and tell users to use &OwnedFd instead of &BorrowedFd<'_> in situations where they know they want the borrower to be able to try_clone().
  • Do add it, but give it a more verbose name, like BorrowedFd::try_clone_into_owned_fd() or so. Document that users should use .as_fd() instead if they just want to copy the existing fd, and that users should be careful when using resources outside the static scope of the borrow.

Manishearth added a commit to Manishearth/rust that referenced this issue Oct 4, 2021
…r=joshtriplett

Add a `try_clone()` function to `OwnedFd`.

As suggested in rust-lang#88564. This adds a `try_clone()` to `OwnedFd` by
refactoring the code out of the existing `File`/`Socket` code.

r? `@joshtriplett`
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue Dec 11, 2021
…r=joshtriplett

Add a `try_clone()` function to `OwnedFd`.

As suggested in rust-lang#88564. This adds a `try_clone()` to `OwnedFd` by
refactoring the code out of the existing `File`/`Socket` code.

r? `@joshtriplett`
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue Dec 11, 2021
…r=joshtriplett

Add a `try_clone()` function to `OwnedFd`.

As suggested in rust-lang#88564. This adds a `try_clone()` to `OwnedFd` by
refactoring the code out of the existing `File`/`Socket` code.

r? ``@joshtriplett``
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue Jan 25, 2022
…r=joshtriplett

Add a `try_clone()` function to `OwnedFd`.

As suggested in rust-lang#88564. This adds a `try_clone()` to `OwnedFd` by
refactoring the code out of the existing `File`/`Socket` code.

r? `@joshtriplett`
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue Jan 25, 2022
…r=joshtriplett

Add a `try_clone()` function to `OwnedFd`.

As suggested in rust-lang#88564. This adds a `try_clone()` to `OwnedFd` by
refactoring the code out of the existing `File`/`Socket` code.

r? ``@joshtriplett``
sunfishcode added a commit to sunfishcode/rust that referenced this issue Jan 27, 2022
Following up on rust-lang#88564, this adds documentation explaining why
`BorrowedFd::to_owned` returns another `BorrowedFd` rather than an
`OwnedFd`. And similar for `BorrowedHandle` and `BorrowedSocket`.
@sunfishcode
Copy link
Member

#88794 adding try_clone() to OwnedFd has now landed.

Thinking about Copy and Clone on BorrowedFd, even though most uses in practice don't need Copy and Clone, it does come up occasionally. For example, in rustix, while most public functions take Fd: AsFd, internally it uses BorrowedFd to reduce the amount of generic code, and whenever it needs to pass a BorrowedFd to more than one system call, it uses Copy to make a copy. It could call as_fd() instead, but that feels unnecessary; BorrowedFd really is safely copyable.

So my inclination for this issue is to say that BorrowedFd::to_owned returning a BorrowedFd is fine. It is surprising, but it doesn't do anything dangerous. And removing it would mean making BorrowedFd non-Copy, which would also be surprising, since BorrowedFd is trivially and safely copyable. #93354 is a PR to address the surprising part by documenting it.

However, this is very much a judgement call, and I'm open to input here.

matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue Mar 2, 2022
…rrowedfd-toowned, r=joshtriplett

Add documentation about `BorrowedFd::to_owned`.

Following up on rust-lang#88564, this adds documentation explaining why
`BorrowedFd::to_owned` returns another `BorrowedFd` rather than an
`OwnedFd`. And similar for `BorrowedHandle` and `BorrowedSocket`.

r? `@joshtriplett`
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue Mar 2, 2022
…rrowedfd-toowned, r=joshtriplett

Add documentation about `BorrowedFd::to_owned`.

Following up on rust-lang#88564, this adds documentation explaining why
`BorrowedFd::to_owned` returns another `BorrowedFd` rather than an
`OwnedFd`. And similar for `BorrowedHandle` and `BorrowedSocket`.

r? ``@joshtriplett``
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue Mar 2, 2022
…rrowedfd-toowned, r=joshtriplett

Add documentation about `BorrowedFd::to_owned`.

Following up on rust-lang#88564, this adds documentation explaining why
`BorrowedFd::to_owned` returns another `BorrowedFd` rather than an
`OwnedFd`. And similar for `BorrowedHandle` and `BorrowedSocket`.

r? ```@joshtriplett```
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue Mar 2, 2022
…rrowedfd-toowned, r=joshtriplett

Add documentation about `BorrowedFd::to_owned`.

Following up on rust-lang#88564, this adds documentation explaining why
`BorrowedFd::to_owned` returns another `BorrowedFd` rather than an
`OwnedFd`. And similar for `BorrowedHandle` and `BorrowedSocket`.

r? ````@joshtriplett````
Dylan-DPC added a commit to Dylan-DPC/rust that referenced this issue Mar 3, 2022
…rrowedfd-toowned, r=joshtriplett

Add documentation about `BorrowedFd::to_owned`.

Following up on rust-lang#88564, this adds documentation explaining why
`BorrowedFd::to_owned` returns another `BorrowedFd` rather than an
`OwnedFd`. And similar for `BorrowedHandle` and `BorrowedSocket`.

r? `````@joshtriplett`````
@sunfishcode
Copy link
Member

As mentioned in #93354, we looked at several options for what to do about to_owned here, but just documenting it seems to be the least bad option. That's now implemented.

#88794 added try_clone() to OwnedFd.

There isn't a BorrowedFd::try_clone or TryFrom<BorrowedFd> for OwnedFd or equivalent yet. There's nothing fundamentally wrong with it, however I think we'll need to look at use cases to shape the API. For now, one can construct a temporary ManuallyDrop<OwnedFd> and try_clone that, and if you find yourself doing that, please also file an issue about it.

bors added a commit to rust-lang-ci/rust that referenced this issue Jun 15, 2022
…iplett

Add a `BorrowedFd::try_clone_to_owned` and accompanying documentation

Add a `BorrowedFd::try_clone_to_owned`, which returns a new `OwnedFd` sharing the underlying file description. And similar for `BorrowedHandle` and `BorrowedSocket` on WIndows.

This is similar to the existing `OwnedFd::try_clone`, but it's named differently to reflect that it doesn't return `Result<Self, ...>`. I'm open to suggestions for better names.

Also, extend the `unix::io` documentation to mention that `dup` is permitted on `BorrowedFd`.

This was originally requsted [here](rust-lang#88564 (comment)). At the time I wasn't sure whether it was desirable, but it does have uses and it helps clarify the API. The documentation previously didn't rule out using `dup` on a `BorrowedFd`, but the API only offered convenient ways to do it from an `OwnedFd`. With this patch, the API allows one to do `try_clone` on any type where it's permitted.
workingjubilee pushed a commit to tcdi/postgrestd that referenced this issue Sep 15, 2022
Add a `BorrowedFd::try_clone_to_owned` and accompanying documentation

Add a `BorrowedFd::try_clone_to_owned`, which returns a new `OwnedFd` sharing the underlying file description. And similar for `BorrowedHandle` and `BorrowedSocket` on WIndows.

This is similar to the existing `OwnedFd::try_clone`, but it's named differently to reflect that it doesn't return `Result<Self, ...>`. I'm open to suggestions for better names.

Also, extend the `unix::io` documentation to mention that `dup` is permitted on `BorrowedFd`.

This was originally requsted [here](rust-lang/rust#88564 (comment)). At the time I wasn't sure whether it was desirable, but it does have uses and it helps clarify the API. The documentation previously didn't rule out using `dup` on a `BorrowedFd`, but the API only offered convenient ways to do it from an `OwnedFd`. With this patch, the API allows one to do `try_clone` on any type where it's permitted.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-io Area: `std::io`, `std::fs`, `std::net` and `std::path` C-bug Category: This is a bug. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

2 participants