# Letterpress cheating in Rust 0.9

Posted on January 14, 2014 by Tommy McGuire
Labels: rust

With the release of Rust 0.9 last week, I have updated my ports of Jeff Knupp's Creating and Optimizing a Letterpress Cheating Program in Python and re-executed the still-crappy benchmarks.

All of these programs look for all of the words that can be made from a given set of letters, based on the system dictionary. The argument was "asdwtribnowplfglewhqagnbe", which produces 7440 results from my dictionary with a possible 33,554,406 combinations made from those letters.

## Results

Performance this time has not changed drastically, in comparison to the Rust 0.8 version. On the other hand, I upgraded the software on my laptop and have new versions of Python and gcc.

Language Program Rust 0.6
Duration
(seconds)
Rust 0.7
Duration
(seconds)
Rust 0.8
Duration
(seconds)
Rust 0.9
Duration
(seconds)
Python alternatives/presser_one.py 49.1 48.6 47.8 39.0
Rust anagrams-hashmap-wide 9.3 15.4 12.1 19.6
Rust anagrams-vectors-wide 11.8 13.1 12.2 16.8
Nimrod alternatives/nimrod_anagrams 12.3
Python alternatives/presser_two.py 12.8 12.6 12.3 11.6
Rust anagrams-vectors 8.0 8.2 11.9 8.1
Rust anagrams-hashmap 6.0 35.5 7.2 7.0
Rust anagrams-hashmap-mmap 4.8 10.6 7.3 6.3
C alternatives/anagrams-vectors 8.0 5.8 5.8 6.0
Python alternatives/presser_three.py 6.0 6.3 6.0 5.8
Rust anagrams-vectors-tasks 27.1 13.8 4.2 4.6
Rust anagrams-djbhashmap 2.8
C alternatives/anagrams-hash 0.9 1.0 1.0 0.9

The various programming languages are:

• Python: Python 2.7.3 for previous versions, 2.7.5 this time.
• C: gcc 4.6.3 for the prior runs, 4.8.1 this time, all with -O3.
• Nimrod: Nimrod is another new programming language that I recently played with; this is version 0.9.2 compiled with -d:release.
• Rust: for this run, I used Rust 0.9, compiled with -O.

Major changes this time around are with the *-wide parallel Rust versions, which got somewhat slower. Possibly, this is due to the changes for the libnative/libgreen split; those two programs are somewhat more chatty than the *-tasks Rust programs. (Disconcertingly, I noted that the timings, particularly for those parallel versions, are not as consistent as they have been.) One newcomer is anagrams-djbhashmap, which has a single thread using an insecure implementation of Rust's HashMap trait. I am not sure what happened with anagrams-djbhash-tasks, which should be in the same range as anagrams-vectors-tasks; they use the same style of parallelism, and the faster hashmap implementation should not hurt it.

## Changes

The changes for Rust 0.9 were, again, not entirely trivial. I realize that, from the viewpoint of the Rust developers, the language may be stabilizing. But out here, I am not seeing it.

For the moment, I performed the minimal changes to my Rust code in order to get it to compile. With the change to external iterators last time, and the recent changes to the behavior of the do keyword (which surprised me; I had not read anything about it on the mailing list), the guts of the programs are now being called as:

    combinations::each_combination(letters, i, |combo| { ... });

To tell the truth, I am not sure if that has performance implications. In the near future, I intend to rewrite each_combination with some other style of interface.

One major, and much-looked-for, change is in the I/O interfaces. The new version is much nicer. (It was in the 0.8 release, under std::rt, although it was not complete.) There are, however, shades of Java:

    let path = Path::new("anadict.txt");    let file = File::open(&path);    let mut bufferedFile = BufferedReader::new(file);    for line in bufferedFile.lines { ... }

The other major change was the removal of the M:N (green-threaded) scheduler to a library and the addition of a (native) 1:1 Rust-task-to-O/S-thread scheduler library. At this time, I do not know which is the default, so I don't know what behavior to expect.

There were a host of syntactic changes with Rust 0.9:

• The declaration of a closure changed: "lt : &fn (l:&T,r:&T)->bool" became "lt : |l:&T,r:&T| -> bool".
• The attribute #[fixed_stack_segment] was removed, following the removal of split stacks.
• The module "link" attribute became "crate_id"; "#[ link(name = "bisect", vers="1.0") ];" became "#[ crate_id = "bisect#1.0" ];". (For the love of god, why? That's horrible.)
• The wildcard when pattern-matching an enum value is now ".." instead of "*".
• str.word_iter is now str.words. str.iter is now str.chars. stream became Chan::new (or SharedChan::new, which is very nice).
• fmt! is officially replaced by format!, with better format strings.
• self is no longer a valid lifetime.

Most of the changes are minor, but still are changes.

Some slightly more important changes were:

• Feature gates were added, and module use clobs ("use std::*;") were moved behind one. The clobs were nice, but being explicit is better anyway.
• Something happened with extern and visibility: "extern { fun mmap(..." became "extern { pub fun mmap(...".
• "Single-element tuple-like structs can no longer be dereferenced to obtain the inner value. A more comprehensive solution for overloading the dereference operator will be provided in the future."

As a result,

   fn drop(&mut self) { unsafe { libc::close(**self); } }

becomes

  fn drop(&mut self) { unsafe { match self { &FileDescriptor(fd) => libc::close(fd) }; () } }
• As mentioned above, do only works with proc's, "in order to make it obvious what the cost of do is". So, calling with_c_str is currently:
  let fd = filename.with_c_str( |cs| { libc::open(cs, libc::O_RDONLY as libc::c_int, 0) } );

Leaving the question, when should proc's be used? Is with_c_str a missed case?

Also, do width.times() {...} becomes for _ in range(0, width) {...}.

• NumCast is now based on ToPrimitive, and they return Option rather than a primitive type.

In "News of the Weird", I think something odd is happening with mutablity and pattern matching.

    let dict_file = File::open_mode(&dict_path, Truncate, Write);    match dict_file {        Some(f) => f.write_str( "hello" ),        None => fail!("not a file")    }

results in a "cannot borrow immutable local variable as mutable" error, as expected. But,

    let mut dict_file = File::open_mode(&dict_path, Truncate, Write);    match dict_file {        Some(f) => f.write_str( "hello" ),        None => fail!("not a file")    }

also results in a "cannot borrow immutable local variable as mutable" error, and

    let dict_file = File::open_mode(&dict_path, Truncate, Write);    match dict_file {        Some(mut f) => f.write_str( "hello" ),        None => fail!("not a file")    }

works, while

    let mut dict_file = File::open_mode(&dict_path, Truncate, Write);    match dict_file {        Some(mut f) => f.write_str( "hello" ),        None => fail!("not a file")    }

results in a "variable does not need to be mutable" warning. I need to take this up on the mailing list; it may be a bug.

Speaking of which, complex.rs, the program I wrote to play with Rust's operator overloading traits, is breaking rustc 0.9.

  rustc -L . -O complex.rs   task 'rustc' failed at 'index out of bounds: the len is 1 but the index is 1', /home/mcguire/soft/rust/src/rust-0.9/src/librustc/middle/trans/common.rs:1225  error: internal compiler error: unexpected failure  This message reflects a bug in the Rust compiler.   We would appreciate a bug report: https://github.com/mozilla/rust/wiki/HOWTO-submit-a-Rust-bug-report  note: the compiler hit an unexpected failure path. this is a bug  note: try running with RUST_LOG=rustc=1 to get further details and report the results to github.com/mozilla/rust/issues  task '' failed at 'explicit failure', /home/mcguire/soft/rust/src/rust-0.9/src/librustc/lib.rs:453

I suspect it has already been fixed.

Site proudly generated by Hakyll.