Live data from Hacker News

Four years with Rust

words.steveklabnik.com

191–199 of 199 posts

Re: Four years with Rust

#191
post #190

Earlier quoted context omitted.

It's still runtime memory management. That makes it garbage collection in my book. Sure it's not as involved as tracing, the prototypical form of GC, but it's still something that requires a runtime check each time that resource is used or a binding to it goes out of scope, similar to aspects in generational GC.

The point is that while both reference counting and garbage collection are types of automatic memory management, the reverse isn't true. From a 1976 paper on automatic memory management https://www.cs.purdue.edu/homes/hosking/690M/deutsch.pdf "Automatic reclamation of storage no longer in use is done by the following two techniques: * Garbage collection * Reference counting" Note that these are two separate items and…

And http://dl.acm.org/citation.cfm?id=356854 (pdf: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.468...) treats reference counting as a "way to distribute GC overhead time".

I'm not saying the term always is used that way in academia. I'm saying that it's sometimes used that way, and that it's valid to call RC a form of GC.

Ironically I said that RC was a form of GC to avoid precisely this argument, because if I say "Rust doesn't have GC" I'll have a bunch of folks telling me that RC is a form of GC. This is largely irrelevant to the point I was making, which explicitly distinguished between RC and tracing ("magic") GC -- which I suspected (but wasn't sure) was what was being asked about.

Re: Four years with Rust

#192

I would appreciate if anyone can share your dev setup for Rust. I tried Rust and Racer long time ago and it's not a pleasant experience.

A single tmux window, with vim on the left (with no special IDE plugins). On the right, autocall.zsh watches ./src and runs 'cargo build' whenever a file changes. (More specifically, I autocall running in a small pane up top and have it run cargo build piped through less [but you need to fake this as a tty to get cargo to output colors] in a specific, larger pane on the bottom.

> get cargo to output colors

cargo build --color=always

Re: Four years with Rust

#193

Earlier quoted context omitted.

A single tmux window, with vim on the left (with no special IDE plugins). On the right, autocall.zsh watches ./src and runs 'cargo build' whenever a file changes. (More specifically, I autocall running in a small pane up top and have it run cargo build piped through less [but you need to fake this as a tty to get cargo to output colors] in a specific, larger pane on the bottom.

> get cargo to output colors cargo build --color=always

It might be fixed now but when I set it up either that didn't exist or even that couldn't convince cargo to output color control chars to a non-tty pipe.

Re: Four years with Rust

#194
post #172
post #154

Earlier quoted context omitted.

Does that let you work with the default type? What I was doing in c#: function(T1 thing) { //do normal T1 stuff here var foo = arg as T2; if (foo != null) foo.T2Stuff(); //more normal T1 stuff } If I'm understanding your example I would have to wrap all the T1 stuff in a match.

Yes, you do. There is no idea of a 'default' type here, because each variant of the enum is treated exactly the same. Your C# example assumes an inheritance relationship between T1 and T2 (i.e. T2 inherits from T1, so you can pass it to something expecting a T1 reference). Rust doesn't have type inheritance, so the only way you can define relationships between types is to have something external tell you how to packa…

Thanks.

The above example was actually using two entirely different interfaces. It seems like rust is quite similar to the OO subset I prefer to use. In this particular case it would have been a better match because traits can implement methods.

Rust is on my "to learn" list over the holidays.

Re: Four years with Rust

#195

Earlier quoted context omitted.

> get cargo to output colors cargo build --color=always

It might be fixed now but when I set it up either that didn't exist or even that couldn't convince cargo to output color control chars to a non-tty pipe.

Fair enough. I just know that it worked with tup, for whatever reason I was playing with that instead of just entr+cargo:

ls src/*.rs | entr -c cargo build

(entr is pretty awesome :)

Re: Four years with Rust

#196
post #88

Earlier quoted context omitted.

> There's still too much that has to be done with unsafe code. But the unsafe situations are starting to form patterns. I think as long as those patterns can be abstracted out and moved into thoroughly-vetted libraries with a safe interface, unsafe code isn't really a problem. I expect that getting Rust's standard libraries to a place where regular applications very rarely need to create their own unsafe code blocks…

Agreed. And considering that the most common uses of unsafe are for collections, I think a limited number of vetted libraries completely realistic. Rolling your own collections is typically not that great anyway; how much better is your linked list/hash table/skip list than everyone elses?

As long as there are 40+ collections data structures like in Java.

Re: Four years with Rust

#197
post #188
post #154

Earlier quoted context omitted.

Does that let you work with the default type? What I was doing in c#: function(T1 thing) { //do normal T1 stuff here var foo = arg as T2; if (foo != null) foo.T2Stuff(); //more normal T1 stuff } If I'm understanding your example I would have to wrap all the T1 stuff in a match.

Yes, you'd have to wrap it in a match. But maybe you don't have to. If T1 is the normal case, then there's a type in Rust called Result that handles this kind of pattern. fn potentially_failing(thing: Result ) { thing.map(|foo| success_case(foo)); } this will only execute in the success case and ignore the error case map_err does the same, but only touching the error case

Thanks, that's pretty cool.

Re: Four years with Rust

#198
post #173

Earlier quoted context omitted.

Where does LLVM do that? LLVM does hoist of invariant code out of loops in the Loop Invariant Code Motion and Loop Strength Reduction phases.[1] But that's not enough. This isn't an invariant situation. Consider a matrix multiply, the most common operation in number-crunching. You're indexing through three 2D matrices along both axes. The indices are usually controlled by FOR statements, so the compiler knows the ran…

LICM is very relevant: it allows hoisting various dimensions of the check up to the loop with the relevant induction variable, meaning LLVM only has to handle things of the pattern: for i in 0..X { if !(i It is capable of doing this with the various induction variable passes it has, showing that i There's also the IRCE (inductive range check elimination) [0] pass—which isn't listed in that document—that should help e…

I started writing a test case, and discovered that crate "algebloat" won't even compile on stable rust 1.14.0. (It uses features "rustc_private" and "test".) It looks like this crate was never finished.

This is the other problem with not having built-in multidimensional arrays. There are so many implementations to choose from. Here's the list of all 34 Rust matrix math packages.[1]

Looking at crate "matrixmultiply", it's all unsafe code.[2] That's because it's C written in Rust:

    pub unsafe fn sgemm(
        m: usize, k: usize, n: usize,
        alpha: f32,
        a: *const f32, rsa: isize, csa: isize,
        b: *const f32, rsb: isize, csb: isize,
        beta: f32,
        c: *mut f32, rsc: isize, csc: isize)
Arrays? What arrays? Raw pointers are good enough, right? What could possibly go wrong?

Still trying to find a package with a matrix multiply in safe Rust code with the subscript checks optimized out.

Update: checked crate "ndarray". Indexing is unsafe.[3]

Update: checked crate "matrices". Empty project.

Update: Checked "scirust" - more raw pointer manipulation.[4]

Not finding real-world matrix libraries in which all those fantastic checking optimizations are used and working. I'd like to see that stuff in action.

[1] https://libraries.io/search?keywords=matrix&languages=Rust [2] https://docs.rs/crate/matrixmultiply/0.1.13/source/src/gemm.... [3] https://github.com/bluss/rust-ndarray/blob/master/src/dimens... [4] https://github.com/indigits/scirust/blob/master/src/matrix/m...

Re: Four years with Rust

#199
post #173

Earlier quoted context omitted.

Where does LLVM do that? LLVM does hoist of invariant code out of loops in the Loop Invariant Code Motion and Loop Strength Reduction phases.[1] But that's not enough. This isn't an invariant situation. Consider a matrix multiply, the most common operation in number-crunching. You're indexing through three 2D matrices along both axes. The indices are usually controlled by FOR statements, so the compiler knows the ran…

LICM is very relevant: it allows hoisting various dimensions of the check up to the loop with the relevant induction variable, meaning LLVM only has to handle things of the pattern: for i in 0..X { if !(i It is capable of doing this with the various induction variable passes it has, showing that i There's also the IRCE (inductive range check elimination) [0] pass—which isn't listed in that document—that should help e…

Here's what the current Rust compiler actually does. Optimization level 3, checking enabled. The question is whether the Rust compiler can optimize out all the checks for a simple matrix multiply without loss of safety.

Rust doesn't know about multidimensional arrays, so they have to be supported in a library. This matrix representation was extracted from the "algebloat" crate:

    pub struct Matrix
    {   data: Vec,
	nrow: usize,
	ncol: usize
    }
Access functions, get and set written in the obvious way:

    #[inline]
    pub fn get(&self, r: usize, c: usize) -> f64
    {   assert!(r 
Matrix multiply, written in the obvious way:

    #[inline]
    pub fn mult(&self, other: &Matrix, result: &mut Matrix)
    {   assert!(self.ncol == result.ncol);  // out of the loop checks
        assert!(self.nrow == result.nrow);
        assert!(self.ncol == other.nrow);
        assert!(self.nrow == other.ncol);
        for r in 0..self.nrow 
        {   for c in 0..self.ncol
            {   let mut tot = 0.0;
                for rr in 0..self.nrow 
                {   tot += self.get(rr,c) * other.get(r,rr); }
                result.set(r, c, tot);
            }
        }   
    }  

Generated code for the inner loop:

        ///
        /// mult - matrix multiply, straightforward approach
        ///
        #[inline]
        pub fn mult(&self, other: &Matrix, result: &mut Matrix)
        {   assert!(self.ncol == result.ncol);  // out of the loop checks
            assert!(self.nrow == result.nrow);
            assert!(self.ncol == other.nrow);
            assert!(self.nrow == other.ncol);
            for r in 0..self.nrow 
            {   for c in 0..self.ncol
                {   let mut tot = 0.0;
                    for rr in 0..self.nrow 
                    {   tot += self.get(rr,c) * other.get(r,rr); }
                    result.set(r, c, tot);
                }
            }   
        }
Generated code for the inner loop. "rustc 1.14.0", optimization level "opt-level = 3", AMD64 instruction set. Debug mode, so asserts should be checked. (Not sure about this; it is possible that opt-level=3, "Aggressive" disables some checking. Documentation is unclear on this.)

    .LBB8_50:
    .Ltmp254:                      ; in Matrix::get()
    	.loc	1 122 0            ; self.data[c + r * self.ncol] 
    	movq	%rdi, %rax
    	mulq	%r11               ; doing the multiply for the subscript every time
    	jo	.LBB8_74           ; and checking it for overflow
    	addq	%rsi, %rax         ; doing the add. No strength reduction 
    	jb	.LBB8_76           ; another check 
    .Ltmp255:
    	.loc	17 1362 0
    	cmpq	%rax, %r9
    	jbe	.LBB8_72           ; and another check
    .Ltmp256:
    	.loc	17 1362 0 is_stmt 0
    	cmpq	%rcx, %r15
    	jbe	.LBB8_78           ; array overflow check
    .Ltmp257:
    	.loc	1 188 0 is_stmt 1
    	incq	%rdi
    .Ltmp258:
    	.loc	1 122 0
    	movsd	(%r12,%rax,8), %xmm1
    .Ltmp259:
    	.loc	1 147 0
    	mulsd	(%rbx,%rcx,8), %xmm1  ; The real work: floating multiply
    	addsd	%xmm1, %xmm0          ; and the add
    .Ltmp260:
    	.loc	18 746 0
    	incq	%rcx
    	cmpq	%r14, %rdi
    	jb	.LBB8_50              ; loop counter check - required
This is relatively decent code. the compiler got rid of multiple checks on the same values. There was some strength reduction of indices, too; only the subscript that's traversing the "wrong way" generated a multiply. The ones that are advancing one element at a time along the underlying vector are just adds. About five instructions could come out, but it's not bad code.

I've seen FORTRAN compilers do this kind of matrix multiply with a five instruction inner loop on a mainframe, incrementing pointers in registers for both dimensions. That's the advantage of multidimensional array support.

The point I made about multidimensional arrays stands - the compiler didn't strength reduce the multiply and eliminate the rest of the checks. That requires inferring too much from the user's code.

On the other hand, the code is good enough that using "unsafe" for performance reasons is very seldom justified.

Post reply on HN