[Roadmap 2017] Productivity: learning curve and expressiveness

What follows is a list of problems that I see as a common source of confusion for new or intermediate users. I would love to hear other candidates that aren't listed here.

It's a nice and bold list, I hope other people will add more items to it. See below for some suggestions from me.

for which problems is it worth investing the effort to find and implement those solutions?

Most of them, in few years of work.

The fact that string literals have type &'static str which cannot be readily coerced to String I would like a way for an &'static str to be coerced to a String, ideally without allocation

This is good to make the Rust code a little less noisy (but this doesn't help newbies much).

ref and ref mut on match bindings are confusing and annoying References to copy types (e.g., &u32), references on comparison operations

But we should also be careful avoiding adding too many special cases to the language.

lexical lifetimes on borrows

OK.

Fiddling around with integer sizes Everybody has to agree that it's annoying to deal with usize vs u32 and so forth This has been discussed numerous times and there are even more complex trade-offs than usual But it seems like some kind of widening (perhaps with optional linting)

On this topic I think:

  • There's a need of a built-in safe cast or in the Prelude;
  • Implicit or ways to index arrays/vectors/slices with values that can be safely coerced to uint.

#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, etc)]

The code to manually implement PartialEq, Eq, PartialOrd, Ord, Add, Sub, etc is quite verbose.

explicit types on statics and constants

One way to improve the situation is to accept code like (two different small features):

// This is expecially handy when you have many items in the array:
const DATA: [u8; _] = [1, 2, 3];

const DIRECTIONS: [u8; 4] = b"LRUD";

Below I list few more things that I think could help Rust become more handy, more succinct, while retaining its safety.


Looking at code like this, I'd like improvements in the type inferencer, so there's no need to add that "::" at the end:

println!("Total: {}", (0i32 ..)
                      .map(|n| n * n)
                      .take_while(|&n| n < 100)
                      .filter(|&n| is_odd(n))
                      .sum::<i32>());

The syntax for struct literals is sometimes too much repetitive and long:

#![feature(static_in_const)]
#![allow(dead_code, unused_variables)]

struct Item {
    name: &'static str,
    weight: usize,
    value: usize
}

const ITEMS: [Item; 22] = [
    Item { name: "map",                    weight: 9,   value: 150 },
    Item { name: "compass",                weight: 13,  value: 35  },
    Item { name: "water",                  weight: 153, value: 200 },
    Item { name: "sandwich",               weight: 50,  value: 160 },
    Item { name: "glucose",                weight: 15,  value: 60  },
    Item { name: "tin",                    weight: 68,  value: 45  },
    Item { name: "banana",                 weight: 27,  value: 60  },
    Item { name: "apple",                  weight: 39,  value: 40  },
    Item { name: "cheese",                 weight: 23,  value: 30  },
    Item { name: "beer",                   weight: 52,  value: 10  },
    Item { name: "suntancream",            weight: 11,  value: 70  },
    Item { name: "camera",                 weight: 32,  value: 30  },
    Item { name: "T-shirt",                weight: 24,  value: 15  },
    Item { name: "trousers",               weight: 48,  value: 10  },
    Item { name: "umbrella",               weight: 73,  value: 40  },
    Item { name: "waterproof trousers",    weight: 42,  value: 70  },
    Item { name: "waterproof overclothes", weight: 43,  value: 75  },
    Item { name: "note-case",              weight: 22,  value: 80  },
    Item { name: "sunglasses",             weight: 7,   value: 20  },
    Item { name: "towel",                  weight: 18,  value: 12  },
    Item { name: "socks",                  weight: 4,   value: 50  },
    Item { name: "book",                   weight: 30,  value: 10  },
];

Improevement in compile-time execution of code could help, but while this is more DRY, it generates a Vec instead of a better [Item; 22]:

const ITEMS2: Vec<Item> =
    [("map",                    9,   150),
     ("compass",                13,  35 ),
     ("water",                  153, 200),
     ("sandwich",               50,  160),
     ("glucose",                15,  60 ),
     ("tin",                    68,  45 ),
     ("banana",                 27,  60 ),
     ("apple",                  39,  40 ),
     ("cheese",                 23,  30 ),
     ("beer",                   52,  10 ),
     ("suntancream",            11,  70 ),
     ("camera",                 32,  30 ),
     ("T-shirt",                24,  15 ),
     ("trousers",               48,  10 ),
     ("umbrella",               73,  40 ),
     ("waterproof trousers",    42,  70 ),
     ("waterproof overclothes", 43,  75 ),
     ("note-case",              22,  80 ),
     ("sunglasses",             7,   20 ),
     ("towel",                  18,  12 ),
     ("socks",                  4,   50 ),
     ("book",                   30,  10 )]
    .iter()
    .map(|&(n, w, v)| Item { name: n, weight: w, value: v })
    .collect();

I'd like some macro in Prelude to create handy associative array literals:

macro_rules! map(
    { $($key:expr => $value:expr),+ } => {
        {
            let mut m = ::std::collections::HashMap::new();
            $(
                m.insert($key, $value);
            )+
            m
        }
     };
);

fn main() {
    let names = map!{ 1 => "one", 2 => "two" };
    println!("{} -> {:?}", 1, names.get(&1));
    println!("{} -> {:?}", 10, names.get(&10));
}

I'd like enums of chars too:

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum MapItem {
    Empty,
    Wall,
    Up,
    Down,
    Start,
    Goal,
    Path,
}

match c {
    '#' => MapItem::Wall,
    'S' => MapItem::Start,
    'G' => MapItem::Goal,
    'D' => MapItem::Down,
    'U' => MapItem::Up,
    ' ' => MapItem::Empty,
    _   => MapItem::Empty, // Error!
}

match *item {
    MapItem::Wall  => '#',
    MapItem::Start => 'S',
    MapItem::Goal  => 'G',
    MapItem::Down  => 'D',
    MapItem::Up    => 'U',
    MapItem::Path  => '*',
    MapItem::Empty => ' ',
}

In D language you can write this, and then you don't need the two not-DRY conversion tables:

enum MapItem : char {
    Wall  = '#',
    Start = 'S',
    Goal  = 'G',
    Down  = 'D',
    Up    = 'U',
    Path  = '*',
    Empty = ' '
}

Also it's worth taking a look at Ada language enumerations, they show several nice features. They allow code similar to this, that is very handy in lot of situations:

enum MapItem: char { '#', 'S', 'G', 'D', 'U', '*', ' ' }
let arr1: [MapItem; 4] = ['#', 'D', 'U', '*'];
let arr2: [MapItem; 4] = "#DU*";
enum TrueMapItem : MapItem { 'S', 'G', 'D', 'U' } // Subset.

This kind of code with literals of subsets of values is quite useful and helps catch some bugs at compile-time.


This line of Python code should be simpler (and shorter) in Rust:

assert [10, 20, 30].index(20) == 1

Currently you have to write:

assert_eq!([10, 20, 30].iter().position(|&x| x == 20).unwrap(), 1);


I'd like a slice fill shorcut in Rust:

arr[] = 10; // D language code

Arrays.fill(arr, 10); // Java code

arr[2 .. $] = 10; // D code

In Rust you write something like this, that is longer and more bug-prone:

for i in 0 .. arr.len() { arr[i] = 10; }
for i in 2 .. arr.len() { arr[i] = 10; }

I'd like this supported in Rust:

fn foo(x: u64) -> u32 {
    u32::from(x % 1_000)
}

This isn't using try_from because with Value Range Analysis the compiler sees that "x % 1_000" always fit in a u32.

Similar code in D language:

uint foo(in ulong x) {
    return x % 1_000;
}

This can't be done, but it's sometimes useful:

for n in (10u32 .. 28).step_by(2).rev() {}

for n in (27u32 ... 10).step_by(-2) {}

This point is more speculative. Currently you have to write:

let a: u32 = 10;
let b = 3;
let c = 20;
let minimum = *[a, b, c].iter().min().unwrap();

But when the length of the iterator is known at compile-time and it's longer than zero, it should not return Option<> but the value (or a reference to the value).

But you can't change the return type like that. So how do we solve this problem?

With a fixed-size lazy iteration?

let minimum: u32 = *[a, b, c].fixed_iter().min();

With a specific function that requires a static length inside the iterator?

let minimum: u32 = *[a, b, c].iter().fixec_min();

In general the compile-time knowledge of the length of an array is a precious information that should be kept and used as much as possible, and not thrown away immediately.

In D language you can do similar things, but I don't know if they are simple enough to do in Rust.


Collections like Array, slice, Vec, Deque, Circular buffers and more could have a indices() method, so instead of:

for i in 0 .. arr.len() { ...
(0 .. arr.len()).map(|i| ...

You can write:

for i in arr.indices() { ...
arr.indices().map(|i| ...

In Ada and Go languages there are similar features.

A standard method like indices() is also useful for library-defined matrices and tensors, that have more complex indexes:

for (r, c) in my_matrix.indices() { ...


In Rust I'd like safe functions like the safe Java operation Float.floatToRawIntBits ( https://docs.oracle.com/javase/7/docs/api/java/lang/Float.html#floatToRawIntBits(float) ).

Currently you need to use an unsafe mem::transmute or unsafe unions. The four functions convert:

f32 => u32
f64 => u64
f32 <= u32
f64 <= u64

I'd like this code to compile:

let n: i32 = 100;
let mut arr = [0u32; usize::try_from(n).unwrap()];

On slices I'd like an uniq() method similar to:

fn sort_uniq<T: Ord + PartialEq>(arr: &mut [T]) -> &[T] {
    arr.sort();

    let ln = arr.len();
    if ln <= 1 { return arr; }

    // Avoid bounds checks by using raw pointers.
    let p = arr.as_mut_ptr();
    let mut r: usize = 1;
    let mut w: usize = 1;

    while r < ln {
        unsafe {
            let p_r = p.offset(r as isize);
            let p_wm1 = p.offset((w - 1) as isize);
            if *p_r != *p_wm1 {
                if r != w {
                    let p_w = p_wm1.offset(1);
                    std::mem::swap(&mut *p_r, &mut *p_w);
                }
                w += 1;
            }
        }
        r += 1;
    }

    &arr[.. w]
}

I'd also like a lazy uniq() function like in D language.


I'd like some Slice Length Analysis. While this feature could do many useful things, at its basic level it allows handy code like:

fn main() {
    let v1: [u32; 4] = [10, 20, 30, 40];
    let a1: [u32; 2] = v1[1 .. 3];
}

I like to chain iterators on multiple lines. But when you need to sort the items you need to break the chain. So I'd like some way to sort avoiding breaking the chain of iterators (to do this the sort of the D language returns a SortedRange that you can convert to an array with a release() method).

In Python language there's also the sorted() function that converts any iterable in a list, sorts it, and returns it. It's a simile yet handy feature. Doing the same on Rust iterator chains looks handy.


In Rust I miss some common algorithms you find in the C++ std lib and D language std lib. One set of functions I miss is the ones that handle a Rust Vec like a sorted set, like insert, remove, intersect, union, difference, etc. I'd also like a rotate() function like in C++/D.


I often write ".collect::<Vec<_>>()", so I'd like a standard shortcut, like ".to_vec()" standard method for iterators, or something similar.


In the Rust Prelude I'd like a Python-like iterator comprehensions macro. This is going to simplify iterator chains, expecially when you need to iterate on two iterators in some kind of cartesian product.

I mean Python code like:

>>> [i * j for i in xrange(5) for j in xrange(6)]
[0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 0, 2, 4, 6, 8, 10, 0, 3, 6, 9, 12, 15, 0, 4, 8, 12, 16, 20]

In Rust you can use flat_map(), but it becomes a little messy:

fn main() {
    let data = (0 .. 5)
               .flat_map(|i| (0 .. 6)
                             .map(move |j| i * j))
               .collect::<Vec<_>>();
}

With a Rust Prelude macro it could become something like:

fn main() {
    let data = iter!{ i * j, for i in 0 .. 5, for j in 0 .. 6 }
               .collect::<Vec<_>>();
}

Or even

fn main() {
    let data = iter!{ i * j, for i in 0 .. 5, for j in 0 .. 6 }.to_vec();
}

This macro could also accept if clauses, like in Python and Haskell.


Python (and D language) have simple means to join a lazy iterable of strings, this is a very common operation that I miss in Rust:

from itertools import imap
a_lazy_iterable = imap(str, xrange(10))
print "".join(a_lazy_iterable)

(In Rust you have to first convert the iterable in a Vec, this is wasteful).


The way D language denotes the length of the array with a $ is:

v[$ - 2] = 10;

In Rust I suggest to add a syntax like:

v[# - 2] = 10;

It's very handy in lot of cases (I discussed those cases in a past post, but so far nothing has moved on this handy feature). Currently you have to use v.len() that is not DRY, and it's much less ergonomic in many cases. D language also offers a way to define the $ or # for library defined matrixces, and so on. This syntax is rather important if you want to use Rust for scientific code.

5 Likes