How do I avoid "temporary value created here" errors when creating an array?

"consume" basically means to transfer ownership of a value to something (like a function), and not hand it back; that value is then dropped once the new owner is done with it (e.g. said function returns). Once the value is dropped, you cannot hold references to it - that's what the String and &str scenario we're discussing here illustrates.

1 Like

Okay, so if I am understanding correctly, could I say this? "Consume refers to a pattern of ownership transfer where the ownership is not transferred back before the variable binding is dropped."

I have learned that the only I really learn the language around a language is to try to describe it back to those who know better.

The formatted String is dynamically allocated, and must live on the heap. If it lives on the heap, something has to own that value or else it gets dropped. A &str is a "view" (or slice as Rust calls it) on top of a String - it's a pointer to the String's underlying u8 Vec and a length coupled together (called "fat pointer"). But it doesn't own the String, it's just a pointer (reference) to it, and therefore doesn't keep it alive - you need something else to own it (and keep it alive) while you have a &str to it.

3 Likes

You get to cheat a little with literal strings like "foo". These get the full type &'static str, where 'static is a lifetime that exists for the entire program, and the actual memory is owned by the compiled binary itself in a .rodata section or similar.

2 Likes

You could probably leave out the "before the variable binding is dropped" part. You pass ownership and never get it back - the value is "consumed", like a black hole consumes matter :slight_smile:.

1 Like

Sneaky little static devils. That, in a nutshell, is why string literals tend not to break, eh? Probably why you have to copy string literal into a String as well, and can't just wrap a new string around one. (If I understand that part correctly).

1 Like

Okay, so dropping's not part of the consuming per se. Just so long as you transfer ownership away, and never get it back, then it's consumed.

Now I just need to figure out where "self" bit that @juleskers mentioned comes in. Or perhaps he meant self as an example, is in foo vs &foo, rather than self per se. Because an argument foo would mean taking ownership (and probably consume it), and an argument &foo would mean no possibility of getting consumed, since it's a reference...which is not a pointer, but means that it is borrowed and ownership will be returned and the compiler won't bother moving the data because of it. Did I get that right?

I think I say per se too much.

You can't wrap a string literal in a String because a String allows mutation (including growing).

1 Like

You know, Rust is much more difficult to learn than most languages. In addition to the language, and the language around the language, it also has a large amount of concepts around the language that are entirely unique to it.

I think that is also what Rust's biggest advantage is. The new concepts around the language lead to abilities (super-powers!) that I have never seen in a language before.

Case in point: I use Unreal Engine 4 and I really like it, but I spend just as much time (more, when trying to publish) dealing with crashes and very squirrely bugs as I do creating. Those bugs are in large part due to errors in C++ code that would not have been possible in Rust. (SO many null pointer dereferences, sheesh).

1 Like

Ah, okay. So then if there were such a thing as a mutable static string reference (is there?) then you could wrap a String around that without copying?

Generally "consume" also means destroyed/dropped, but the high level picture is you gave away ownership and can no longer get access to that value. So in theory that value can be roundtripped back to you via some other return value but you may not even know it. The basic notion is you gave it away to someone - that someone "consumed" (or "took") it as far as you're concerned, you don't own it anymore and may not have any access to it.

As for self, if you have a function taking self (and not &self or &mut self) that doesn't return the self back, that function "consumes" self. It's really no different from consuming other types of values.

1 Like

No such thing - cannot have mutable static references. Good thing too, because otherwise it could lead to bugs. Also note that String is not just mutable, but also growable. That's a bit different than just being mutable - you can, e.g., have a mutable string slice but only thing you can do is in-place updates - no expansion/reallocation.

1 Like

I'm not sure where these side-clauses like "as long as it doesn't return the self back" are coming from.

I wouldn't get caught up on small nuances in terminology here. Any time a Rust programmmer uses a phrase that sounds vaguely like "consume" or "use up" or "give to," I'd bet 5 dollars they're probably talking about moves.

Why? Because moving is an absolutely central part of Rust's design. It is something a Rust coder deals with and must deal with every minute of every day, to the point where it begins to transform the way you think and solve problems. What else is there to talk about?

fn some_function(x: Vec<u8>) -> Vec<u8> { x }

fn main() {
    let old_guy = vec![1,2,3];
    let new_guy = some_function(old_guy);
}

As far I am concerned, old_guy is "consumed" in this example. Maybe aliens came down and robbed it of its identity. Maybe it moved when you were in preschool, and you two just don't recognize each other after puberty. Maybe new_guy is the philosophical zombie that was created after old_guy was teleported into some_function, requiring it to be reconstructed out of an entirely new set of bits. You can make up your own story.

But if you are to start adding lines to main that borrow these values (mutably or not), then ultimately what matters is that old_guy and new_guy each die a separate death:

  • old_guy died because it was accessed by value.

    • "old_guy was consumed / was used up / was given away / was moved."
  • new_guy died because it left scope without ever having been accessed by value.

    • In this case, Drop::drop() gets called to clean up the waste and free resources.
    • "new_guy / was freed / was destroyed / was lost / was dropped."

and that no borrow can outlive either of these life ending events.

(Almost all seemingly tricky edge cases can be interpreted as falling under the first category; such as ::std::mem::forget(x);, let y = x;, return x;, and, ironically, ::std::mem::drop(x))

2 Likes

@ExpHP So, in your last parenthetical statement right at the end are you saying that each of those expressions is an example of moving a value? If so, then how is a drop a move?

fn main() {
    let x = vec![1,2];
    drop(x);
}

fn drop<T>(_: T) { }

In main, x is moved into drop's argument list. This is the exact moment at which a borrow of x in main would become invalid. What happens after that is unrelated.

(unrelated but simple; yes, ::std::mem::drop really is defined as an empty function!. What happens is that the argument to drop immediately falls out of scope and thus is Drop::dropped automatically)

(Edit: Reading my previous post over I see it gets a bit confusing near the end; that's due to sloppy editing. Initially, my post was more focused on there being only two ways for objects to "die;" but then I reworked most of it after remembering that "partial moves" are a thing...)

1 Like

Haha, yeah! That's always a fun part of joining a new discourse-based forum :slight_smile:

Sounds correct! Like a piece of delicious pie, you are not getting it back after the other "consumer" consumes (=eats) it. (Though the consumer may give you something in exchange, like money, or give a piece back, like the strawberry that was on top)

Unfortunately, yes. You already mentioned that you are probably missing "something fundamental". The fundamental point is that someone/something must own anything you are referencing. Rust doesn't keep it around otherwise.
Answer yourself this: after the initialiser is done, who owns the String that is referenced by the array? Who did you tell "here, keep this String for me, so that my array can point at it"?

Format!() returns an Owned String, it is then up to you to decide who (which variable) keeps it around (=which variable has ownership)

If you only want to return a reference from the array initialiser, you are trying to say "here, have a reference to some str somebody else owns", but your 'somebody else' is the temp created by format. That temp is automatically cleaned up after the initialiser is done (the scope of the format-value ends at the end of the statement, at ]; because you don't assign it to anything )
As soon as the cleanup happens, your reference becomes invalid. It points at empty space!

1 Like

My bad! I should have used foo and &foo, like you guessed. self has a very different meaning for Trait methods, which doesn't apply here. My bad!

That's how I felt too, until I learned that a lot of it was, apparently, common knowledge in academia since the early eighties, and was used in non-mainstream 'hardcore' languages like OCaml, Smalltalk, Haskell and Lisp (none of which I speak, but I'm starting to wonder if I should learn)

+1 for mentioning the correct "language around the language". This is definitely what I was trying to explain! I Should have used that word earlier, but it isn't a clear enough concept in my own brain yet :frowning:

Move semantics aren't really a new concept, it has already been best-practice in C for years, but Rust is the first to formalise it so clearly and the first to actually have tooling that enforces it.
In C Most people vaguely know about it, but forget all the time. Every time the borrow-checker tells you "value does 't live long enough", that would have been a crashing bug if you had been writing C :smile: (see the Unreal engine experience of @CleanCut)

P.S. I noticed some of you made a lot of individual replies above. Discourse allows you to reply+quote multiple posts in one reply; just highlight/select the butbit you wish to quote, click the "quote" button that appears, and Discourse will add it to your currently-in-progress reply :smile:

1 Like

Oh! Cool! Thanks! I deduced that you meant s/but/part/ -- it works great!

Whoops, that's what you get for posting from mobile on the train :slight_smile:
At least I learned the <del> syntax

Back on-topic: how goes the wrapping-your-brain-around-rust part?
Has it clicked yet why returning a reference-to-a-temp doesn't (and shouldn't) work?

1 Like