Live data from Hacker News

Rust fact vs. fiction: 5 Insights from Google's Rust journey in 2022

opensource.googleblog.com

201–210 of 233 posts

Re: Rust fact vs. fiction: 5 Insights from Google's Rust journey in 2022

#201
post #189

Earlier quoted context omitted.

> The fact that ownership is a part of public API is a good thing A libraries next version which switches up some internal representations memory handling should ideally not mess up your application, but it also mandates a higher refactor rate when you are only working within your application’s boundaries. These are worthwhile tradeoffs for the niche rust is targeting, but not for every use case. I’m not saying Rust…

> A libraries next version which switches up some internal representations memory handling should ideally not mess up your application It doesn't have to because internal memory representation can and should be abstracted out, and Rust gives a plethora of tools to do that. Your argument works against against static typing in general. The next version changes the address representation from String to Address (in manag…

> Your argument works against against static typing in general

I don’t think this argument works. Ad absurdum a very strong type system would even specify the implementation itself, making any change breaking — is that good? No, it isn’t as the useful property of the type system is no longer there. I don’t agree that this usefulness line is behind “ownership annotations” — that’s what you would have to convince me of.

Re: Rust fact vs. fiction: 5 Insights from Google's Rust journey in 2022

#202
post #189

Earlier quoted context omitted.

> The fact that ownership is a part of public API is a good thing A libraries next version which switches up some internal representations memory handling should ideally not mess up your application, but it also mandates a higher refactor rate when you are only working within your application’s boundaries. These are worthwhile tradeoffs for the niche rust is targeting, but not for every use case. I’m not saying Rust…

> A libraries next version which switches up some internal representations memory handling should ideally not mess up your application It doesn't have to because internal memory representation can and should be abstracted out, and Rust gives a plethora of tools to do that. Your argument works against against static typing in general. The next version changes the address representation from String to Address (in manag…

If version 1 of your library has a function that returns a reference to T, then that constrains your choices of implementation for the underlying data structures containing Ts more than they would be constrained in a garbage-collected language. So unless there is to be a ban on functions returning simple references, Rust is always going to be a little less flexible in this respect. That is fine and expected given the overall design goals of the language, but there's no point pretending that it's not the case.

The simplest concrete example, I guess, would be a function that returns &'str. There are plenty of Rust APIs out that have functions with this type signature.

Re: Rust fact vs. fiction: 5 Insights from Google's Rust journey in 2022

#203

Earlier quoted context omitted.

Please demonstrate a practical and memory safe systems programming language without borrowing. I'd be delighted to see it, because right now I am not aware of any practical way to have memory safe regions without static tracking of borrowing from these regions. It's either that or runtime checking.

This might be of interest: https://verdagon.dev/blog/first-regions-prototype It uses region-based static analysis without borrow checking: it doesn't impose aliasability-xor-mutability per object, or even per-region. Though, if you'd like to move the goalposts further to no form of borrowing at all, then I recommend looking at languages like Forty2 and Verona, they might be what you're looking for.

I have already spent more time than I wanted on reading through verbose but elusive articles about Vale, without any insight into how this actually happens.

I have already spent too much time trying to compile Vale compiler which is a weird mix of Scala and C++ with a small Vale driver. Once it is actually written in Vale without segfaults, I'll revisit the language again.

Thanks for the Verona recommendation.

Re: Rust fact vs. fiction: 5 Insights from Google's Rust journey in 2022

#204
post #195

Earlier quoted context omitted.

I’m talking about checked exceptions — they are absolutely analogous to a Result in Rust, that is they are part of the type signatures. It is no longer the example you are talking about re panics vs Result.

Checked exceptions are indeed similar to Result, but as such they also have the same issue of “coloring” the call stack, so I don't really see what's the argument you're making here…

My whole point: error handling is decided at the caller-end trivially. They can try-catch/use ?/just unwrap, whatever it doesn’t concern the implementation. Async/blocking is not like that, as it can itself recursively contain similar “decision-points”, and the final caller cares about all of them, as it might change semantics.

Re: Rust fact vs. fiction: 5 Insights from Google's Rust journey in 2022

#205
post #202

Earlier quoted context omitted.

> A libraries next version which switches up some internal representations memory handling should ideally not mess up your application It doesn't have to because internal memory representation can and should be abstracted out, and Rust gives a plethora of tools to do that. Your argument works against against static typing in general. The next version changes the address representation from String to Address (in manag…

If version 1 of your library has a function that returns a reference to T, then that constrains your choices of implementation for the underlying data structures containing Ts more than they would be constrained in a garbage-collected language. So unless there is to be a ban on functions returning simple references, Rust is always going to be a little less flexible in this respect. That is fine and expected given the…

> If version 1 of your library has a function that returns a reference to T, then that constrains your choices of implementation for the underlying data structures containing Ts more than they would be constrained in a garbage-collected language

This is IMHO a good thing. If it returns a reference to T, it means it still owns it and allows only temporary usage of it. This is semantics of ownership and does not have anything to do with memory management. If you wanted to allow sharing for unspecified lifetime, sure, you can. There is Rc/Arc.

I've fixed plenty of bugs in code written in managed languages, where a reference to T was handed out from a library (because there is no other choice - everything is a reference) and then someone stored it for longer than it was valid, leading to a logical equivalent of use-after-free.

E.g. get an entity object managed by Hibernate. Pass it up outside of the context of Hibernate session. It will likely blow up because the object references a session that's now closed. Rust ownership model would prevent exactly that problem.

I find this "flexibility" of managed languages actually a problem in large codebases, similarly how flexibility of goto is universally considered bad. It severely hinders maintainablity. It allows to pass references freely and create implicit, complex, often cyclic, reference graphs which are very hard to reason about.

In my Rust code 99% of objects don't need shared ownership. But managed languages make shared ownership the default, optimizing for the edge-case.

BTW, your statement can be rephrased to: "If version 1 of your library has a function that accepts a reference to T, then that constrains your choices of values of T more than it would be constrained in a dynamically typed language."

You may say that you can use Any / Variant / Object in a statically typed language to overcome that limitation. True, and similarly you can use Rc/Arc/Copy types in Rust.

This is all the same thing. It just takes static typing to the next level. Not only it allows to express constraints on values, but it also allows to express constraints on time they can be used.

Re: Rust fact vs. fiction: 5 Insights from Google's Rust journey in 2022

#206
post #202

Earlier quoted context omitted.

If version 1 of your library has a function that returns a reference to T, then that constrains your choices of implementation for the underlying data structures containing Ts more than they would be constrained in a garbage-collected language. So unless there is to be a ban on functions returning simple references, Rust is always going to be a little less flexible in this respect. That is fine and expected given the…

> If version 1 of your library has a function that returns a reference to T, then that constrains your choices of implementation for the underlying data structures containing Ts more than they would be constrained in a garbage-collected language This is IMHO a good thing. If it returns a reference to T, it means it still owns it and allows only temporary usage of it. This is semantics of ownership and does not have a…

> This is a good thing.

This is dogmatic. In can be a good thing sometimes, in some domains. In other instances it can just be a pain.

Let's say I'm using a 'names' library that provides the following utility function:

    first_name(name: &'a str) -> &'a str
My code happily uses this function (which we can assume is not performance critical). At some point, the author of the library notices that there are cultures where the nearest analogue of a 'first name' is not always a contiguous substring of the full name. To fix this bug they must change the function's type. They may choose e.g.:

    first_name(name: &'a str) -> Cow
I'd like to update my version of the library to get the bug fix, but can't do this for free. I have to update the calling code, and possibly even change some of my own internal data representations. This contrasts with pretty much any GCed language. For example, in Go the type of the function would just be

    func firstName(name string) string
and the bug fix would require no change in the API.

Now let's relate this example back to what you originally said in response to kaba0:

>> [kaba0:] A libraries next version which switches up some internal representations memory handling should ideally not mess up your application

> It doesn't have to because internal memory representation can and should be abstracted out, and Rust gives a plethora of tools to do that.

The above is a simple example of why this is not true. Any time you write a function that returns a reference, you are limiting the changes you can make to internal representations (both in the library itself and the calling code) without making a breaking API change.

Please don't respond by saying "this API was badly designed in the first place!" Most languages don't give you the opportunity to design APIs badly in this particular way. If all APIs were perfectly designed on day one then of course we'd never have to worry about API changes.

Again, none of this is to bash Rust. I just think it is important to be realistic about the downsides as well as the upsides of Rust's ownership system.

Re: Rust fact vs. fiction: 5 Insights from Google's Rust journey in 2022

#207
post #204

Earlier quoted context omitted.

Checked exceptions are indeed similar to Result, but as such they also have the same issue of “coloring” the call stack, so I don't really see what's the argument you're making here…

My whole point: error handling is decided at the caller-end trivially. They can try-catch/use ?/just unwrap, whatever it doesn’t concern the implementation. Async/blocking is not like that, as it can itself recursively contain similar “decision-points”, and the final caller cares about all of them, as it might change semantics.

But if you catch an error in the middle of the call-stack, then the caller don't have access to this error, so this has the exact same semantic implication as well! And the final caller would sometimes care about that, but no luck, yet I've never seen anyone complaining about how `catch` is terrible for that reason…

Of course it doesn't happen too often, but nor does your recursive async/blocking function example (been using async/await for a decade now, and I've never encountered the issue in actual code) and I suspect that for most purpose, using `block_on` in the blocking function is the sensible thing to do, and it has the same role as a `catch`: the upper function has no way to know there was actually some async stuff under the hood.

Re: Rust fact vs. fiction: 5 Insights from Google's Rust journey in 2022

#208

Earlier quoted context omitted.

Color functions are just not the right road to go down. Something akin to Javas new green threads would be better, or Go style coroutines. The path Rust is going means async becomes viral, and is something I dislike a lot about JavaScript[0] and other languages I’ve worked in[1]. I’d love to see Rust avoid this trap. [0]: I work in TypeScript in actuality not sure which to use here. It’s certainly by far the language…

"Color" in this case just means signature. Of course you can't mix signatures - why would you want to? You might as well argue that Rust shouldn't have had any more than a single integer argument to every function. Async in rust essentially desugars to a return trait impl.

That isn't quite true. An async signature forces it's caller to be async as well. This is because of it's semantics. Most signatures don't infect their callers signatures like that. async is qualitatively different in this regard.

Re: Rust fact vs. fiction: 5 Insights from Google's Rust journey in 2022

#209
post #206

Earlier quoted context omitted.

> If version 1 of your library has a function that returns a reference to T, then that constrains your choices of implementation for the underlying data structures containing Ts more than they would be constrained in a garbage-collected language This is IMHO a good thing. If it returns a reference to T, it means it still owns it and allows only temporary usage of it. This is semantics of ownership and does not have a…

> This is a good thing. This is dogmatic. In can be a good thing sometimes, in some domains. In other instances it can just be a pain. Let's say I'm using a 'names' library that provides the following utility function: first_name (name: &'a str) -> &'a str My code happily uses this function (which we can assume is not performance critical). At some point, the author of the library notices that there are cultures wher…

By writing:

    first_name(name: &'a str) -> &'a str
you promised the caller that the output is built directly from the input slice. That is your choice. I'll show you later that you didn't have to.

Similarly by writing this in Go:

    func firstName(name string) string
you promised the function accepts a string.

Then you want to change the semantics (the contract) of the function, break your promise and you complain you have to change the signature. You can't do that in any statically typed language.

In Go's case, if you suddently wanted to change the memory representation of name to something else than string, e.g. to a name struct:

    func firstName(first_name name) name
then obviously you have to change the signature. This is the same problem.

If you don't want your caller to be affected by lifetimes, just don't specify them in the signature:

    fn first_name(name: String) -> String
this is perfectly fine in Rust.

You may say it might be slow because it forces a copy, and forces a particular string implementation. So as I said, Rust gives you tools to abstract out the implementation details:

    fn first_name(name: impl AsRef) -> impl AsRef {
       // all are correct:
       // return name;
       // return "foo";
       // return String::from("foo");
    }
The flexibility goes actually much further than just relaxing the lifetimes. With this signature I actually can change the name representation from String to any type that can be exposed as a slice, with no additional runtime penalty like virtual calls, which you'd need otherwise in Go/Java.

> I just think it is important to be realistic about the downsides as well as the upsides of Rust's ownership system.

So the downside is that it offers you more choices and allows to express more constraints in the signatures, and you can choose wrong. I guess that's quite ok in a general purpose language that wants to be applicable to many different niches.

Re: Rust fact vs. fiction: 5 Insights from Google's Rust journey in 2022

#210
post #206

Earlier quoted context omitted.

> This is a good thing. This is dogmatic. In can be a good thing sometimes, in some domains. In other instances it can just be a pain. Let's say I'm using a 'names' library that provides the following utility function: first_name (name: &'a str) -> &'a str My code happily uses this function (which we can assume is not performance critical). At some point, the author of the library notices that there are cultures wher…

By writing: first_name (name: &'a str) -> &'a str you promised the caller that the output is built directly from the input slice. That is your choice. I'll show you later that you didn't have to. Similarly by writing this in Go: func firstName(name string) string you promised the function accepts a string. Then you want to change the semantics (the contract) of the function, break your promise and you complain you ha…

As I said in the previous comment:

>Please don't respond by saying "this API was badly designed in the first place!" Most languages don't give you the opportunity to design APIs badly in this particular way. If all APIs were perfectly designed on day one then of course we'd never have to worry about API changes.

I did edit in that paragraph ~5 minutes after submitting the comment, so apologies if you missed that.

>fn first_name(name: impl AsRef) -> impl AsRef {

As I said previously, "...unless there is to be a ban on functions returning simple references..." If you're willing to eat all that extra generic ceremony, then yes, you can make Rust APIs that are flexible w.r.t. ownership. However, you can't control the code style of the libraries that you're using.

> With this signature I actually can change the name representation from String to any type that can be exposed as a slice, with no additional runtime penalty like virtual calls, which you'd need otherwise in Go/Java.

Off topic, but you could do this in both Java and Go via generics.

Post reply on HN