4 ms·
What annoys me about borrowing is, that my default mode of operating is to not mutate things if I can avoid it, and I go to some length in avoiding it, but Rust
by zelphirkalt 1y ago
What annoys me about borrowing is, that my default mode of operating is to not mutate things if I can avoid it, and I go to some length in avoiding it, but Rust then forces me to copy or clone, to be able to use things, that I won't mutate anyway, after passing them to another procedure. That creates a lot of mental and syntactical overhead. While in an FP language you are passing values and the assumption is already, that you will not mutate things you pass as arguments and as such there is no need to have extra stuff to do, in order to pass things and later still use them.
Basically, I don't need ownership, if I don't mutate things. It would be nice to have ownership as a concept, in case I do decide to mutate things, but it sucks to have to pay attention to it, when I don't mutate and to carry that around all the time in the code.
- arnsholt 1y agoOwnership serves another important purpose: it determines when a value is freed.
- zelphirkalt 1y agoI guess it is then a necessary complication of the language, as it doesn't have garbage collection, and as such doesn't notice, when values go out of scope of all closures that reference them?
- steveklabnik 1y agoYes, but it’s more subtle than that. What Rust does is track when the object goes out of scope, and will make sure that any closures that reference it live for a shorter time than that. Sort of backwards of what you’re asking.
- pclmulqdq 1y agoThe borrow checker is a compile-time garbage collector. If you think about it in that sense, you can understand a lot of the ways it restricts you.
- timeon 1y ago> While in an FP language you are passing values By passing values do you mean 'moving'? Like not passing reference?
- zelphirkalt 1y agoYes, I guess in Rust terms, that is called moving. However, when I have some code that "moves" the value into another procedure, then the code after that call, can no longer use the moved value. So I want to move a value, but also be able to use it after moving it, because I don't mutate it in that other function, where it got moved to. So it is actually more like copying, but without making a copy in memory. It would be good, if Rust realized, that I don't have mutating calls anywhere and just lets me use the value. When I have a mutation going on, then of course the compiler should throw error, because that would be unsafe business.
- asa400 1y agoI'm not sure how what you're describing is different from passing an immutable/shared reference. If you call `foo(&value)` then `value` remains available in your calling scope after `foo` returns. If you don't mutate `value` in foo, and foo doesn't do anything other than derive a new value from `value`, then it sounds like a shared reference works for what you're describing? Rust makes you be explicit as to whether you want to lend out the value or give the value away, which is a design decision, and Rust chooses that the bare syntax `value` is for moving and the `&value` syntax is for borrowing. Perhaps you're arguing that a shared immutable borrow should be the default syntax. Apologies if I'm misunderstanding!
- NIckGeek 1y agoCouldn't you just pass a reference to your value (i.e. `&T`)? If you absolutely _need_ ownership the function you call could return back the owned value or you could use one of the mechanisms for shared ownership like `Rc<T>`. In a GC'd functional language, you're effectively getting the latter (although usually a different form of GC instead of reference counting)
- 1y ago
- pornel 1y agoBorrowing isn't for mutability, but for memory management and limiting data access to a static scope. It just happens that there's an easy syntax for borrowing as shared or exclusive at the same time. Owned objects are exclusively owned by default, but wrapping them in Rc/Arc makes them shared too. Shared mutable state is the root of all evil. FP languages solve it by banning mutation, but Rust can flip between banning mutation or banning sharing. Mutable objects that aren't shared can't cause unexpected side effects (at least not any more than Rust's shared references).
- vlovich123 1y agoIt sounds like you may not actually know Rust then because non-owning borrow and ownership are directly expressible within the type system: Non-owning non mutating borrow that doesn’t require you to clone/copy: fn foo(v: &SomeValue) Transfer of ownership, no clone/copy needed, non mutating: fn foo(v: SomeValue) Transfer of ownership, foo can mutate: fn foo(mut v: SomeValue) AFAIK rust already supports all the different expressivity you’re asking for. But if you need two things to maintain ownership over a value, then you have to clone by definition, wrapping in Rc/Arc as needed if you want a single version of the underlying value. You may need to do more syntax juggling than with F# (I don’t know the language so I can’t speak to it) but that’s a tradeoff of being a system engineering language and targeting a completely different spot on the perf target.
- zelphirkalt 1y agoCan you give examples of the calls for these procedures? Because in my experience when I pass a value (not a reference), then I must borrow the value and cannot use it later in the calling procedure. Passing a reference of course is something different. That comes with its own additional syntax that is needed for when you want to do something with the thing that is referred to.
- Tuna-Fish 1y ago> Because in my experience when I pass a value (not a reference), then I must borrow the value and cannot use it later in the calling procedure. Ah, you are confused on terminology. Borrowing is a thing that only happens when you make references. What you are doing when you pass a non-copy value is moving it. Generally, anything that is not copy you pass to a function should be a (non-mut) reference unless it's specifically needed to be something else. This allows you to borrow it in the callee, which means the caller gets it back after the call. That's the workflow that the type system works best with, thanks to autoref having all your functions use borrowed values is the most convenient way to write code. Note that when you pass a value type to a function, in Rust that is always a copy. For non-copy types, that just means move semantics meaning you also must stop using it at the call site. You should not deal with this in general by calling clone on everything, but instead should derive copy on the types for which it makes sense (small, value semantics), and use borrowed references for the rest.
- pjmlp 1y agoDepends on the FP language though, they are only values in the logic sense, they can be reference as well, hence the various ways to do equality.