Remix.run Logo
sensen7 5 days ago

>In that era you actually did need to "fight" to get obviously correct code to compile because the checking is only looking at the lexical structure.

NLL's final implementation (Polonius) hasn't landed yet, and many of the original cases that NLL were meant to allow still don't compile. This doesn't come up very often in practice, but it sure sounds like a hole in your argument.

What does come up in practice is partial borrowing errors. It's one of the most common complaints among Rust programmers, and it definitely qualifies as having to fight/refactor to get obviously correct code to compile.

steveklabnik 5 days ago | parent [-]

> What does come up in practice is partial borrowing errors.

For some people. For example, I personally have never had a partial borrowing error.

> it definitely qualifies as having to fight/refactor to get obviously correct code to compile.

This is not for sure. That is, while it's code that could work, it's not obviously clear that it's correct. Rust cares a lot about the contract of function signatures, and partial borrows violate the signature, that's why they're not allowed. Some people want to relax that restriction. I personally think it's a bad idea.

5 days ago | parent | next [-]
[deleted]
hmry 5 days ago | parent | prev [-]

> Rust cares a lot about the contract of function signatures, and partial borrows violate the signature

People want to be able to specify partial borrowing in the signatures. There have been several proposals for this. But so far nothing has made it into the language.

Just to give an example of where I've run into countless partial borrowing problems: Writing a Vulkan program. The usual pattern in C++ etc is to just have a giant "GrahpicsState" struct that contains all the data you need. Then you just pass a reference to that to any function that needs any state. (of course, this is not safe, because you could have accidental mutable aliasing).

But in Rust, that just doesn't work. You get countless errors like "Can't call self.resize_framebuffer() because you've already borrowed self.grass_texture" (even though resize_framebuffer would never touch the grass texture), "Can't call self.upload_geometry() because you've already borrowed self.window.width", and so on.

So instead you end up with 30 functions that each take 20 parameters and return 5 values, and most of the code is shuffling around function arguments

It would be so much nicer if you could instead annotate that resize_framebuffer only borrows self.framebuffer, and no other part of self.

steveklabnik 5 days ago | parent [-]

> People want to be able to specify partial borrowing in the signatures.

That's correct. That's why I said "Some people want to relax that restriction. I personally think it's a bad idea."

> The usual pattern in C++ etc is to just have a giant "GrahpicsState" struct that contains all the data you need. Then you just pass a reference to that to any function that needs any state.

Yes, I think that this style of programming is not good, because it creates giant balls of aliasing state. I understand that if the library you use requires you to do this, you're sorta SOL, but in the programs I write, I've never been required to do this.

> So instead you end up with 30 functions that each take 20 parameters and return 5 values, and most of the code is shuffling around function arguments

Yes, this is the downstream effects of designing APIs this way. Breaking them up into smaller chunks of state makes it significantly more pleasant.

I am not sure that it's a good idea to change the language to make using poorly designed APIs easier. I also understand that reasonable people differ on this issue.

sensen7 5 days ago | parent [-]

>Yes, this is the downstream effects of designing APIs this way. Breaking them up into smaller chunks of state makes it significantly more pleasant.

What they're describing is the downstream effect of not designing APIs that way. If you could have a single giant GraphicsState and define everything as a method on it, you would have to pass around barely any arguments at all: everything would be reachable from the &mut self reference. And either with some annotations or with just a tiny bit of non-local analysis, the compiler would still be able to ensure non-aliasing usage.

"functions that each take 20 parameters and return 5 values" is what you're forced to write in alternative to that, to avoid partial borrowing errors: for example, instead of a self.resize_framebuffer() method, a free function resize_framebuffer(&mut self.framebuffer, &mut self.size, &mut self.several_other_pieces_of_self, &mut self.borrowed_one_by_one).

I agree that the severity of this issue is highly dependent on what you're building, but sometimes you really do have a big ball of mutable state and there's not much you can do about it.