The one thing I don't get about rust

In theory, but in practice that can't change the meaning of the same syntax: the compiler can't read your mind! So essentially you're back to trying to add some syntax sugar, but in a way that doesn't annoy everyone else.

For me the most likely option would be something like a let clonable foo = ..., declare it at use site, rather than a trait.

3 Likes

Having something as potentially expensive as a clone happen automatically based on some unmentioned context rules is certainly a bad idea in my opinion.

For the beginner, this is just spelling out disaster when they come from GC languages where let x = y copies the reference, only for them to come here and find out it just silently copies the object.

This is not to mention that even for experienced programmers, it'd be yet another silent factor to keep in mind when programming, and if memory management issues resolved by "just be a good programmer" have taught us anything, programmers aren't perfect at tracking invisible things.

In reality, the solution to this issue is to construct an API which better suits the structure you're dealing with. If you're writing algorithms with your matrix type, take references, not bare objects. The point of a library is to encapsulate all the potentially ugly-to-the-user code, so it's by definition alright if you need to use .clone() here or there. As a library writer trying to write an ergonomic API, the task at hand is to design a library which appeals to the user, not to the writer (although if it does, that's just a nice benefit).

And I agree with other people on feature creep. Declaring that there is to be one way to do a thing in stdlib/corelib and the language not only keeps the mental load low for newcomers, but also prompts future library writers to think twice about the design of their API in an attempt to avoid repeated code which is semantically equivalent.

8 Likes

No standard library types should be AutoClone. Why rock the boat? I don't think it's unfortunate to have to newtype to get AutoClone behavior. Why would it be? It's an opt-in sort of thing for certain cases. In my case I would want to use it on my own struct type.

Yeah, don't do that. It wouldn't bother me, since I spend a lot of time coding in Swift, but would bother others.

Don't do it for core types.

Just put it in and then direct scorn toward those who use it :slight_smile:

Sure, but I appreciate your replies over here in the meantime.

Man, you really didn't look at what I wrote. User ergonomics of a library is the impetus for this whole discussion.

Well in swift, let x = y will in fact copy the object if the type is a struct, and not if it's a class. I think the behavior is the same in C#. People are quite used to this. What's the big deal?

Well that's just T and T&. I suppose a proper conservative Gc<T> would also be Copy.

1 Like

I do feel like issues like this is why Rust has no UI libraries to speak of -- or very few at least: its incredibly hard to write an easy to use UI library in Rust that uses native components (for example) with a clean, easy to work with API, because Rust imposes all of these limitations and requirements that just make everything a lot harder than it should be. I know people have tried getting around this stuff by using macros, for example, but that's more of a hack than a solution. If we want people to write high-quality UI libraries in Rust instead of - I dunno - going for Dart or Node/React, something has to give. Relm kinda does this but its still not as simple as I'm sure people would like it to be. But that might just be me. I've heard lots of complaints from some people about the extreme difficulties that they face in trying to write a good UI in Rust because of all the extra constraints and requirements you effectively have to hack around because the normal way you'd create a retained-mode GUI library just doesn't work in Rust like it does in C/C++ or Python for example.

1 Like

Check out my GUI library which I posted at the beginning. I think it's actually pretty easy to use.

It does look nice, and if the "accessible" goal means accessibility for assistive technologies, I wholeheartedly approve. Rust lacks so many UIs that allow accessibility, other than GTK and the raw win32 API, that is. All the other ones that allow it are unmaintained. Actually making it accessible will be difficult, especially if your looking to interface with the accessibility APIs of each platform (which is something I would strongly encourage since it means less code you have to write in the long run).

Yes, that's what I meant. I'll clarify in the readme.

Typically you don't want to clone matrices. Why would you clone a matrix? That's a waste of time and memory. It would be a bad matrix library if it was cloning the same data multiple times. You want to share references to them. Shared references are Copy.

But that's not the philosophy of Rust. Here is what the Rust Book says about this:

Rust is for people who crave speed and stability in a language. By speed, we mean the speed of the programs that you can create with Rust and the speed at which Rust lets you write them.

[...]

By striving for zero-cost abstractions, higher-level features that compile to lower-level code as fast as code written manually, Rust endeavors to make safe code be fast code as well.

[...]

Overall, Rust’s greatest ambition is to eliminate the trade-offs that programmers have accepted for decades by providing safety and productivity, speed and ergonomics.

Functional programming is the opposite of auto-cloning. In functional programming, you don't ever clone objects. There is no need because everything is immutable. So you share read-only references.

Rather than auto-cloning, it seems like what you really want is garbage collection, which would allow you to pass objects around freely without worrying about ownership.

13 Likes

Hm, I think the whole "AutoClone would create a new language" topic is a red herring. I think it wouldn't change the language as fundamental as some make it to be and Rust already as a ton of behaviour we push into "implicit" space.

I would even argue that the fact that clone has a rather weak contract on what actually happens during cloning (as opposed to Copy) is much more fundamental than whether you have to clone() or not. Even with a suggestion as the OP has, such things would still be traceable using e.g. IDE tooling for those interested in the details.

In my training work, I already see things like Arc seen as "heavy" types, mostly due to the laborious interaction with them. That's definitely a problem.

I find the reading of "survivorship bias" excessive - in my context, those issues just don't arise outside of a few narrow parts of the codebase. But that doesn't make the OPs problem go away. I'd like to warn here against a traditionalist perspective, which I certainly see building. Rust is still a young language - arguments should be based on a possible future based on collected experience - be it the future of a Rust with e.g. autoclone or with other solutions for the OPs problem.

I can say that explicit clones for e.g. Arc also didn't necessarily add a ton of things for me.

4 Likes

I think the best solution to the ergonomics problem of "cloning stuff into closures" is having an built-in ergonomic syntax for cloning/moving/borrowing individual variables into closures. It would also make it easier to see what closures capture in contexts where you need to be explicit, while reducing the noise of declaring variables for manual captures(either borrows or clones).

5 Likes

But that shouldn't continue or be the new norm! Match ergonomics already confuses learners, and Deref coercions are a regular source of misunderstanding too. Having Arc auto-clone would be yet another another thing to learn and then continuously have to worry about.

Wow, that's one strong assertion. You are labelling people pejoratively who would merely like the language to stay the way they learned and like it, and not change under their feet in directions that contradict its current values.

8 Likes

I personally like the way Copy and Clone works currently, but that's merely my personal feeling, which might change as I work more with Rust.

I see Copy as a guarantee for a lightweight type (that can not just be "copied" but also dropped any time without needing to run a destructor), and !Copy as everything else. If a type could require a destructor in future, it also makes sense to explicitly clone it, I think.

The only thing that sometimes bugged me is that a range isn't Copy even if its boundaries are. But there are reasons for it, and I think I usually run into this problem only when I explicitly use one of the range structs in std::ops in my own data types or APIs. Perhaps the (mental) solution to this is to see the range types in std::ops more as a language-reflection feature only, and instead use my own "range" data types when needed. These could still implement RangeBounds and IntoIterator. This feeling is backed up by the fact that the range structs in std::ops do not offer all combinations of closed and open intervals, such as an open interval where both the lower and the upper boundary are excluded.

1 Like

FWIW, this was specifically listed as something the library team want to fix in a recent blog post.

8 Likes

These are good points, but there is a dividing line: whether allocation can happen (even if it often dosen't with SmartString).

One way to resolve that would be to have a lint for AutoClone happening

That does not seem useful for the reason you stated. But, having AutoClone require that the trait is imported (much like is required for any other method call through a trait) and not have it in the prelude would provide options... almost different dialects of the language, but essentially we already have that.

Is [u8; 1000] a Copy type? Yes, and so is [u8; 1000000]. Unfortunate, but it is what it is.

As someone with a "path" type which is essentially just

union {
    u64,
    Rc<[usize]>,
}

and which uses the former in-place representation the vast majority of the time, I really wish I could call it a Copy type. AutoClone would be perfect. Lacking that, it's almost tempting just to remove the reference-counting and never deallocate (i.e. leak).

3 Likes

I for one love the explicit nature of clone.

Me too. My only problem with clone is that some clones are cheap (e.g. Rc types) and some are expensive, and knowing which is which requires knowing the underlying type, which isn't always straightforward when dealing with unfamiliar code.

1 Like

But [u8; 1000] is still a memcpy(): it's much cheaper than incrementing X Rcs.

Also, I see Copy not as "cheaply copyable" but as "memcpy copyable", it.e POD datatype with no resources held. This view is somewhat supported by the Copy's docs suggesting that you should impl Copy everywhere you can, no mentions of type size. You definitely should not actually copy big things by accident (I think there is a lint for that?) but you still should implement it, because maybe someone will need.

2 Likes

That's... a bizarre argument. Then again, moves are a memcpy and those are implicit even for big types.

I don't see a lint, not even from Clippy: (1) Rust Playground, (2) Rust Playground

Is this true? I imagine the answer is going to depend very much on CPU caches so I'm not even sure this is answerable in general?

It's not yet finalized or stabilized -- see issue 83518.

Example on nightly.

6 Likes