2

Sometimes its useful to create new values for a fixed size array in a loop:

fn foo(u: f64) -> [f64; 3] {
    let mut ret = [-1.0; 3];  // -1 is never used!
    for i in 0..3 {
        ret[i] = some_calculation(u, i);
    }
    return ret;
}

While this works, it's a bit weak to create an array filled with a value which is never used.

An alternative is to manually unroll, but this isn't so nice for larger fixed sized arrays or when the expression is more involved then the example given:

fn foo(u: f64) -> [f64; 3] {
    return [
        some_calculation(u, 0),
        some_calculation(u, 1),
        some_calculation(u, 2),
    ];
}

Does Rust provide a way to do something roughly equivalent Python's list comprehension?

fn foo(u: f64) -> [f64; 3] {
    return [some_calculation(u, i) for i in 0..3];
}

I am a beginner who has very little experience with iterators.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
ideasman42
  • 42,413
  • 44
  • 197
  • 320
  • 1
    ret should be mutable and you don't need to return it explicitly; just "ret" in the last line works just fine. – ljedrz Sep 05 '16 at 06:23

3 Answers3

4

Rust guarantees memory safety in its default mode (outsides unsafe blocks).

In order to do so, it must guarantee that no uninitialized memory is ever accessed, which translates (for arrays) in guaranteeing that they are fully initialized no matter what happens.

A clever analysis could check that your loop will fully initialize it, but would probably not be able to prove it works in more complicated cases, so the experience would be inconsistent, and jarring when a simple change in the function would suddenly cause you to have to come back to the array and fully initialize it now that the compiler can no longer prove it works.

So, instead, Rust took the following approach:

  1. Ask the user to fully initialise the array (by providing a copyable value)
  2. Rely on the optimizer to eliminate redundant writes

In case the second step fails in a particular setup, a user can use unsafe { std::men::uninitialized() } to tell the compiler that it takes it upon itself to guarantee it is fully initialized.

This approach is always safe, often as fast, ... and incredibly annoying when you are unfortunate enough not to be working with a Copy type. In this latter case, a simple strategy is to first build a Vec, and then move its elements into an array with a simple for loop, hopefully the optimizer should elide all the unnecessary stuff afterward.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • Agree `unsafe` in this case would be a last resort *(or only to use in isolated cases when it can be proven to make a tangible difference)*. However AFAICS `unsafe` isn't necessarily the only way. It *should* be possible to use a macro that unrolls an expression into an one that initializes all elements of the array, eg: `[unroll_n!(some_expr(i), i, 3)]`. – ideasman42 Sep 05 '16 at 09:14
  • @ideasman42: We would need a macro wizard for a definite answer, but I am not sure that it is actually feasible to instruct a macro to repeat a piece of code X number of times. The variable construct (`*`) expands as many times as the arguments did, which require the caller to pass as many arguments as necessary (workable for 3, but for 42 it's annoying). – Matthieu M. Sep 05 '16 at 09:44
  • @ideasman42: (Note: in the comment above I purposely ignored the idea of using pattern-matching in the macro implementation and special-casing a small number of array sizes, however it would obviously be a possibility) – Matthieu M. Sep 05 '16 at 12:40
  • Attempt at macro (failed): https://play.rust-lang.org/?gist=338ea1e7655bcfdea8d7e8b24dc5fe61&version=stable&backtrace=0 – Matthieu M. Sep 05 '16 at 13:08
1

If you're okay using an unsafe block, you can do:

fn foo(u: f64) -> [f64; 3] {
    let mut ret : [f64; 3] = unsafe { std::mem::uninitialized() };
    for i in 0..3 {
        ret[i] = some_calculation(u, i);
    }
    return ret;
}
Benjamin Lindley
  • 101,917
  • 9
  • 204
  • 274
  • Though it works, I would not call it a best practice in Rust. – ljedrz Sep 05 '16 at 06:21
  • 1
    Using a direct assignment is only safe for types that don't implement `Drop` (otherwise, the destructor will be called on an uninitialized value!). In general, `ptr::write` should be used to assign a value to an uninitialized memory location. – Francis Gagné Sep 05 '16 at 12:42
0

Arrays in Rust are always initialized: Rust reference, SO question on converting Vecs to arrays, so this shouldn't be a concern (especially if you are after the best Rust practice).

Community
  • 1
  • 1
ljedrz
  • 20,316
  • 4
  • 69
  • 97