Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Of course it allows invalid combinations.

This also compiles:

    let f = std::fs::File::open("/dev/null").unwrap();
    let f: std::os::fd::OwnedFd = f.into();
    let socket: std::net::UdpSocket = f.into();
If you convert a high level object into a low level one, and then back up as another type, then what exactly do you expect the language to do about that?

> "protected from or not exposed to danger or risk."

A computer will do what you tell it to do, not what you intend it to do. Opening a file is way more dangerous than risking errors because "this syscall doesn't work on that fd".

There's also always risk that a syscall will fail at runtime, whether the type of fd is correct or not.

It sounds like you would prefer if UdpSocket From<OwnedFD> should run getsockname() or something to confirm it's of the expected type, but I would prefer not. Indeed, in the general case some perfectly coded `unsafe` code could `dup2()` over the fd, so any checking at UdpSocket creation time is moot; you still don't get the safety you are asking for.





I agree with everything you wrote except for this:

> Indeed, in the general case some perfectly coded `unsafe` code could `dup2()` over the fd, so any checking at UdpSocket creation time is moot; you still don't get the safety you are asking for.

If `unsafe` code breaks safe code's soundness guarantees (let's assume for a second an alternate world in which "fd is of the correct type" is a soundness guarantee Rust makes), the bug is in the `unsafe` code.


Sure, I would strongly recommend against doing something like that. But I would expect it to work in the obvious way, and not be undefined behavior.

E.g. if UdpSocket were to dup() internally its fd A into a fd B, and as_fd() returned B, but all actual recv/send is on fd A, then that would cause worse problems than this.

But say an OS has a sockopt that turns an IPv4 UDP socket into a IPv6 UDP socket. Would it be OK for me to call that on UdpSocket's underlying fd? I'd say yes.

Now if I closed the fd for a UdpSocket from underneath it, I would expect that to be basically UB, if not by spec, then in practice impossible to reason about.

As for unsound, sure. I could be convinced of calling the dup2 thing unsound. Not sure unsound is well enough defined, but basically: don't do it.

Is it unsound to create a UdpSocket from a non-UDP file descriptor? Not in a way that can trigger unsafe, no.


There are two issues here, and you're talking about a different one from the one I'm interested in. Your main issue seems to be this:

> If you convert a high level object into a low level one, and then back up as another type, then what exactly do you expect the language to do about that?

One answer to this would be "prevent it entirely". That's probably not practical for a language like Rust today, though, and I don't really care about that.

What I care about is that it's necessary to do this in the first place. The fact that doing this can be useful and necessary in a case like this suggests that it would be possible to design the types involved so that you don't need these low-level and runtime-unsafe conversions to get the job done.

> It sounds like you would prefer if UdpSocket From<OwnedFD> should run getsockname() or something to confirm it's of the expected type

No, I'm saying the types could be designed to prevent the need for doing this in the first place.


> What I care about is that it's necessary to do this in the first place.

I don't think it is. socket2::Socket has send_to() just as much as UdpSocket does.

(disclaimer: I only looked up the docs, I didn't try to modify the code to strip out needless UdpSocket)

> runtime-unsafe

That's not a thing. You always need to check for errors. seccomp could be blocking your syscalls. Hard drives break such that reads return error.

Getting an Err() from a function does not make it "unsafe", runtime or not.

> the types could be designed to prevent the need for doing this in the first place.

If your type system does not allow you to "bring your own fd (to be managed)", then it's not fit for purpose for the kind of problems Rust aims to solve.

A systems language needs to be able to receive a file descriptor from a C library, and work with it.


> > runtime-unsafe

> That's not a thing.

Yes, it is. See all the work on the subject of "make illegal states unrepresentable". Search for that phrase, it's originally from Yaron Minsky at Jane Street Capital, but it's mainly a pithy characterization of a common goal for languages with strong type systems, like Rust, Haskell, or the ML family. Another way this is expressed is as "static debugging" - the idea that you can debug a significant proportion of a program's bugs statically, using the type system.

That's what I'm referring to here. If it's unfamiliar to you, it will likely take some time to get used to, because it's a significantly different paradigm from the common approach of debugging by running programs, encountering runtime errors, and trying to resolve them. But, instead of getting indignant and objecting to what I'm saying, consider that there might be something for you to learn here.

> You always need to check for errors. seccomp could be blocking your syscalls. Hard drives break such that reads return error.

What do you believe the relevance of this is? You need to check for errors that can't be checked for at compile time. That doesn't mean we should abandon the idea of checking for errors at compile time, or improving the scope of issues that we can detect at compile time.

Errors that are prevented at compile time cannot occur at runtime in principle, and that's an extremely powerful invariant in software development, one that any serious software developer should be aware of, and be able to take advantage of.

Type systems allow you to prove properties of programs that otherwise would need to be debugged and tested at runtime, but to take advantage of that, the types need to be designed appropriately.

> If your type system does not allow you to "bring your own fd (to be managed)", then it's not fit for purpose for the kind of problems Rust aims to solve.

This is a failure of imagination, nothing more. An appropriate type schema for this domain will be able to handle the requirements of the domain.


You're misapplying it.

A language that doesn't let you do safe things, then that's a very different language.

In this case, it would be a language that does not allow creating a UdpSocket object by bringing in your own file descriptor, or it verifies that it's the right type of socket when you do. Which has performance implications without adding any "safe" guarantees.

Say you add this feature, taking the performance hit. Now you need to adjust seccomp policies to allow that. Ok, no biggie. But then I invent UDPv2, and this check fails. The code becomes wrong because of an incorrect assumption about the future.

All without gain. It's not an invalid state, any more than naming a variable "x_squared" but containing x+1 is an invalid state.

You could also imagine stdout to be of a different type if it's line or character buffered, and continue in the direction of a cartesian explosion for all states. Ok… that seems like it'd cause more problems than it'd solve.

> instead of getting indignant

Please don't assume my mental state. You got it wrong.

> This is a failure of imagination, nothing more. An appropriate type schema for this domain will be able to handle the requirements of the domain.

I'm all ears. Note that it also has to support "I got the file descriptor as a libc::c_int from a C library", or it's not fit for purpose.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: