C++ has vector(n, value). c has calloc(). rust has, uh,

Unfortunately you need initialized buffers in safe Rust code when dealing with Readers.

By the way, does Rust impose any restrictions on reading from uninitialized memory?
For example, in C++ it's okay to read garbage (unsigned) chars and the read values should be consistent, but reading anything else is UB.

Yes - it's not possible without unsafe code. There's mem::uninitialized if you need it for some reason (most likely to not pay the cost of initializing), but it is very unsafe, specially for types that have destructors.

There is no relationship between the reasons that Rust no longer has from_elem and the dubious notion that filling a buffer with zeroes is a code smell, and that one needs to use something like collect rather than from_elem does not in any way "make the redundancy apparent", so I would say that's a far stretch for a rationalization, rather than "another way to look at it".

One of the proposed changes to IO suggests a convenience function reading exhaustively to Vec, and covering the remaining need for non-exhaustive/atomic reads by adding method with_extra to Vec. The latter is still unsafe, but much "safer" and ergonomical than doing manual work with arrays on the heap.

But it is a code smell; it's just so pervasive in current system languages that everyone stopped noticing. The example above is where a more principled solution is provided for an often cited case. I see little day-to-day need in contiguous buffers filled with nothing important, and Rust provides ways of doing it for unsafe low-level code where such needs are likely to occur, like implementations of I/O and serialization. In my opinion, the safe interfaces for core data types should induce the client to work with useful data up front rather than get bogged down in specifics of initialization.

But it is a code smell

Your raw assertion doesn't address any of my points as to why your "way to look at it" is not apt. As for the "more principled solution" ... we don't know why the OP wants to fill a buffer with zeroes, so we don't know that this is a solution to hsr problem.

it's just so pervasive in current system languages that everyone stopped noticing.

This doesn't match my 50 years of programming experience, mostly in "system languages". When an array is filled with zeroes, it's usually for a good reason, such as booleans that are initially false or counters that initially "no count". Of course, there are C programmers who habitually call calloc for everything, but not on my teams.

the safe interfaces for core data types should induce the client to work with useful data up front rather than get bogged down in specifics of initialization

Perhaps, but Vec::from_elem was not removed for that reason and the removal does not have that effect since it can easily be emulated. You wrote that "Rust just makes the redundancy apparent" but Rust has nothing to do with it; the redundancy (if that's what you want to call it) of a buffer filled with some value is just as apparent regardless of whether it's done with Vec::from_elem(size, value) or (0..size).map(|_| value).collect()
If you want to "induce" people to use better practices, you need pedagogy; the available interfaces don't "induce" anything.

In these cases, more cumbersome initialization code may provide "syntactic salt" to consider whether a mutable vector filled with initially trivial data is the appropriate solution. Can the vector be large? Is it likely to end up sparsely mutated?

In a number of instances while coding in Rust, I find myself writing cumbersome code, to later find out that a better, more idiomatic, solution was available. I consider it the best programming pedagogy there can be.

1 Like

Can the vector be large? Is it likely to end up sparsely mutated?

If so then perhaps a different data structure should be used, but it's a rather poor programmer who requires "syntactic salt" to consider such things and rather poor programming language design to make things hard just to "induce" programmers to reconsider using arrays ... especially when the answers to your questions might well be "no".

In a number of instances while coding in Rust, I find myself writing cumbersome code, to later find out that a better, more idiomatic, solution was available.

Again, this has nothing to do with Rust ... I've encountered this in dozens of languages. And it doesn't have to be idiomatic; reconceptualizations often lead to the elimination of much complexity. A functional approach can be a powerful engine in this regard ... I suppose many people, such as the OP, would still regard this as "idiomatic".

I consider it the best programming pedagogy there can be.

I had something else in mind:

"ped·a·go·gy ˈpedəˌɡäjē/ noun
the method and practice of teaching, especially as an academic subject or theoretical concept."

Anyway, I find all this ad hoc rationalization tiresome and we are moving further and further off topic, so this is the end of the discussion for me.

Funny that you say that, but AFAIK Rust did introduce a number of said "speed bumps"/"syntax salt" already. One source of salt was Box::new() and box opposed to having a dedicated sigil.

In theory, it prevents people that don't read documentation to write overly long lines. However sometimes brevity is assumed to be end goal and it backfires - like in this case. I think a macro sugar might be in order to prevent further confusion.

Everything you've said is true, but that is not what I asked.

This wasn't just about salt though, it was about making the core language smaller, and that ~T could not use a custom allocator.

1 Like

True, although it wasn't the only instance of making a speed bump in language.

it's hard because you probably don't need to zero your buffer

Rust prevents you from reading uninitialized memory, so by initializing it and using it later you might introduce a security bug into your application

It's impossible (← strong claim there) to use the Reader::read function safely without initializing (e.g. zeroing) your allocation.

1 Like

Ah, but you can use Reader::push instead, probably for exactly this reason? (not sure).

Anyhow, I think being able to init blocks of memory easily is nice, isn't actually an anti-pattern (in my experience), and that being told you don't need to do something (allocate initialized memory) is frustrating when you actually do need to do it (how else do I rig the destination vector for my sparse matrix vector multiplication?). Better to explain, for example, why push is better than read; who wants to do that? :slight_smile:

1 Like

I guess I misunderstood your question then, sorry about that. Would you mind reframing?

1 Like

Ah, it's nice that this was fixed. However, this solution limits you to using Vec, and the push function has a contract with the read function that it won't touch the bytes in its parameter. I opened an issue (#21896) about it.

Despite quite a few claims here, I have yet to see any evidence showing that the design motivation -- rather than merely a consequence -- for something in Rust was to make it harder for people to do something. The only example of this in any language that I'm aware of is the C++ cast syntax. (Aside from languages such as INTERCAL and Befunge where being difficult is a motivation for the whole language.)

I just wanted to clarify in this discussion that the removal of from_elem (along with a few other functions) was not part of any particular agenda about how Rust code "should" be written, but simply a matter of cutting down on API surfaces that we commit to indefinitely.

Heading into 1.0, we've been scrutinizing APIs throughout the standard library, trying to make the more consistent, coherent, and in some cases more minimal -- to ensure we have a solid base to commit to and work from. Even so, we have retained many convenience APIs when their importance was clear.

Previously, we thought that from_elem was a somewhat borderline case in terms of importance as a convenience. This discussion as well as some others has strongly suggested to me that we were wrong, and that from_elem is worth providing. It's a tiny commitment.

(FWIW, if someone here would like to write a very short RFC for its re-inclusion, I'd be glad to help shepherd it.)

6 Likes

I don't really understand how these things work, but is it not possible to generalize vec![val; num] to work for num that are not compile-time constants? That would add the functionality of from_elem, and remove the not-uncommon "why doesn't vec! work the way I thought?" question.

2 Likes