News for hackers. Stuff that matters.
Re:From Julia to Rust by ForHackernews on Saturday June 05, 2021 @06:18PM (#27408871)
I like both languages, but they seem to have extremely different goals and target users to me.

Julia aims to be better than R and Python at statistics and data analysis. It's not there yet, but I could easily see it replacing a great deal of academic use of Numpy and Python in Jupyter notebooks (the 'ju' is Julia).

On the other hand, Rust seems like it's aiming at being a safer alternative to C for low-level systems programming.

I'm sure there's things Julia could learn from Rust, but the design decisions are going to differ wildly because they just aren't trying to do similar things.

Re:From Julia to Rust by awaythrowact on Saturday June 05, 2021 @06:49PM (#27409054)
Fair. But to play devils advocate: one stated goal of Julia is to solve the “two languages” problem where developers prototype in {scripting language} and then reimplement in {system language}. So while Julia is meant to be as easy as Python, it also needs to be as powerful and flexible as Rust (or C or whatever). It’s supposed to be the “best of both worlds” and Rust lives in one of those worlds. If developers are better off prototyping in Python jupyter notebooks and then reimplementing in Rust than they are implementing in Julia, then I’d say the comparison is relevant. Maybe it’s an unfair standard and Julia can be a “better Python” without needing to be a full replacement of a systems language, but then Julia isn’t fully solving the two language problem, right?
Re:From Julia to Rust by hexane360 on Saturday June 05, 2021 @07:25PM (#27409271)
Yes, this is exactly what I was getting at. Speed is one of the reasons to choose Rust/C/C++ over Python, but there's other compelling ones: static types, more control over execution, tight coupling to the OS, etc. To truly solve the two language problem, Julia should have APIs and coding styles which allow greater control, even if they're not used by everyone.

Because it's so closely integrated with C, Python actually does a decent job at this, which IMO is one of the reasons it's been so successful as a general purpose language in addition to a scientific one. I really hope Julia can do the same.

Off the top of my head, here are a few features I think would really improve Julia for general purpose programming:

- Static type checking (JET.jl[1] looks promising)

- Faster startup (again, great strides have been made)

- Tagged, closed unions

- Pattern matching

[1]: https://github.com/aviatesk/JET.jl

Re:From Julia to Rust by eigenspace on Saturday June 05, 2021 @07:55PM (#27409420)
> Pattern matching

MLStyle.jl [1] is quite nice for this and has been around for a while.

> Tagged, closed unions

These are less general than 'real' unions and can be implemented using them. E.g. SumTypes.jl [2] has some macros to make it a bit more convenient to define them, it could use some other quality of life features though.

[1] https://thautwarm.github.io/MLStyle.jl/latest/syntax/pattern...

[2] https://github.com/MasonProtter/SumTypes.jl

Re:From Julia to Rust by hexane360 on Sunday June 06, 2021 @01:20AM (#27410981)
MLStyle.jl is nice, but I think Julia would really benefit from having it enshrined in the language. Multiple dispatch is great for a lot of problems, but sometimes it makes more sense to do your pattern matching inline.

For tagged unions, the difference is in memory layout. A real tagged union type would eliminate indirection and allow more code to be type stable.

For instance,

    mutable struct NotTypeStable
        o::Union{Some{Int}, Nothing}
    end

    function not_type_stable()
        o = returns_option()
        if isnothing(o)
            0
        else
            1
        end
    end
versus:

    enum Option{T}
        Some(T)
        None
    end

    mutable struct TypeStable
        o::Option{Int}
    end

    function type_stable()
        o = returns_option()
        match o
            Some(_) => 1
            None => 0
        end
    end
Both of these features aren't strictly necessary, but they act as a compliment to Julia's existing dynamic mechanisms (multiple dispatch & Union types)
Re:From Julia to Rust by celrod on Sunday June 06, 2021 @01:15PM (#27414589)
Try Julia nightly (1.7):

  julia> @code_warntype not_type_stable()
  MethodInstance for not_type_stable()
    from not_type_stable() in Main at REPL[2]:1
  Arguments
    #self#::Core.Const(not_type_stable)
  Locals
    o::Any
  Body::Int64
  1 ─      (o = Main.returns_option())
  │   %2 = Main.isnothing(o)::Bool
  └──      goto #3 if not %2
  2 ─      return 0
  3 ─      return 1
  
  
  julia> versioninfo()
  Julia Version 1.7.0-DEV.1169
  Commit e5d7ef01b0* (2021-05-26 14:17 UTC)
Note `Body::Int64`. With older Julia versions, it should still be type stable if you did `o === nothing` instead of `isnothing(o)`.
Re:From Julia to Rust by hexane360 on Monday June 07, 2021 @01:13PM (#27425149)
Right, I'm referring to the local o being Any, meaning that it must be behind a pointer lookup. I should have clarified because the more common usage is about the function's return value.
Re:From Julia to Rust by eigenspace on Sunday June 06, 2021 @06:20PM (#27416889)
Here's me copying your second example as closely as possible in julia:

    using MLStyle

    mutable struct TypeStable
        o::Union{Some{Int}, Nothing}
    end

    MLStyle.@as_record TypeStable
    MLStyle.@as_record Nothing

    function type_stable()
        o = returns_option()
        @match o.o begin
            Some(_)   => 1
            Nothing() => 0
        end
    end

    returns_option() = TypeStable(rand(Bool) ? Some(rand(1:10)) : nothing)
And what does the compiler have to say about it?

    julia> Core.Compiler.return_type(type_stable, Tuple{})
    Int64
and

    julia> @code_warntype type_stable()
    Variables
      #self#::Core.Const(type_stable)
      o::TypeStable
      259::Union{Nothing, Some{Int64}}
      return#257::Union{Nothing, Int64}

    Body::Int64
    1 ─       (o = Main.returns_option())
    │         (return#257 = Main.nothing)
    │         (259 = Base.getproperty(o, :o))
    │   %4  = (259 isa Nothing)::Bool
    └──       goto #3 if not %4
    2 ─       (return#257 = 0)
    └──       goto #5
    3 ─ %8  = (259::Some{Int64} isa Some)::Core.Const(true)
    │         %8
    │   %10 = (259::Some{Int64} !== Main.nothing)::Core.Const(true)
    │         %10
    │         (return#257 = 1)
    └──       goto #5
    4 ─       Core.Const(:((error)("matching non-exhaustive, at #= REPL[9]:3 =#")))
    5 ┄       return return#257::Int64

This is on version 1.6.1 for me, but should work fine on earlier releases. I agree that having MLStyle.jl pattern matching bundled into julia would be great!
Re:From Julia to Rust by hexane360 on Monday June 07, 2021 @01:18PM (#27425210)
Doesn't the TypeStable struct just hide the unknown type, not remove it? The type of o.o still needs to be looked up at runtime. Furthermore, TypeStable itself has to be on the heap, which I think means this causes two pointer lookups rather than just one.
Re:From Julia to Rust by eigenspace on Monday June 07, 2021 @01:46PM (#27425630)
> Doesn't the TypeStable struct just hide the unknown type, not remove it?

No, of you look at the typed IR I posted, it says

    259::Union{Nothing, Some{Int64}}
and

    (259 = Base.getproperty(o, :o))
So it's inferred properly to be a small union which julia handles efficiently at runtime:

    julia> @btime type_stable()
      18.988 ns (1 allocation: 32 bytes)
    1
> Furthermore, TypeStable itself has to be on the heap, which I think means this causes two pointer lookups rather than just one.

It's on the heap because you wrote

   mutable struct
in your example and I was just mimicking you as close as possible. If you don't actually want it to be mutable, then you can just remove the keyword mutable and it'll be even faster and have no heap allocations:

    julia> @btime type_stable()
     18.426 ns (0 allocations: 0 bytes)
    1
Re:From Julia to Rust by hexane360 on Monday June 07, 2021 @11:45PM (#27430447)
>small union which julia handles efficiently at runtime

Oh! I didn't realize Julia already optimized small unions.

https://julialang.org/blog/2018/08/union-splitting/ https://docs.julialang.org/en/v1/devdocs/isbitsunionarrays/

I wasn't quite sure whether something like this was possible given the semantics of Julia.

Personally, I'd still prefer something more explicit, so it would be more obvious when a union is being handled efficiently. But given that small unions are optimized, I can see why Julia would make the opposite decision.

This makes me feel a lot better about writing Julia code with union types. Thanks

Re:From Julia to Rust by eigenspace on Tuesday June 08, 2021 @12:50PM (#27437639)
Yep, small union optimizations are a godsend!
Re:From Julia to Rust by yawaramin on Saturday June 05, 2021 @08:01PM (#27409453)
These suggested improvements are really reminding me of OCaml–which actually also has quite close integration with C, like python.

There's even a numerical computing library: https://ocaml.xyz/

Re:From Julia to Rust by duped on Saturday June 05, 2021 @06:57PM (#27409113)
The two language problem exists for a bunch of reasons, some good and some not.

The biggest reason is because some function of the high level language is incompatible with the application domain. Like garbage collection in hot or real-time code or proprietary compilers for processors. Julia does not solve these problems.

Other reasons are practical, like portable executables in Go or Rust (portable in the sense they do not require dependencies on their target systems, usually). Julia does not solve this problem.

Then there are the reasons to use scripting languages over the system languages, like expressive syntax with low cognitive overhead. Julia definitely helps here. But this comes at the cost of execution and startup time. Julia only kind of solves this problem.

So if Julia is trying to make a more performance scripting language then that is admirable. But for most of the projects where I have needed to prototype in a script and implement in a systems language, Julia would not have worked.

Even today I don't have a good reason to use it over MATLAB for day to day work, since I already have the license and their ecosystem is more mature.

Re:From Julia to Rust by eigenspace on Saturday June 05, 2021 @08:01PM (#27409455)
> The biggest reason is because some function of the high level language is incompatible with the application domain. Like garbage collection in hot or real-time code or proprietary compilers for processors. Julia does not solve these problems.

The presence of garbage collection in julia is not a problem at all for hot, high performance code. There's nothing stopping you from manually managing your memory in julia.

The easiest way would be to just preallocate your buffers and hold onto them so they don't get collected. Octavian.jl is a BLAS library written in julia that's faster than OpenBLAS and MKL for small matrices and saturates to the same speed for very large matrices [1]. These are some of the hottest loops possible!

For true, hard-real time, yes julia is not a good choice but it's perfectly fine for soft realtime.

[1] https://github.com/JuliaLinearAlgebra/Octavian.jl/issues/24#...

Re:From Julia to Rust by duped on Saturday June 05, 2021 @08:17PM (#27409543)
Doesn't Julia use a mark-and-sweep GC? How is it possible to use in a soft-real-time context without stopping the world?
Re:From Julia to Rust by eigenspace on Saturday June 05, 2021 @08:43PM (#27409659)
Just because a GC exists doesn't mean you have to use it though. It's just another tool on your belt.

There's various options depending on your problem. You could just hold firmly onto memory, preallocating all your buffers before the program starts and then don't allow them to be GC'd, you could also use stack allocated arrays like in StrideArrays.jl / StaticArrays.jl, or some combination of the above. You could also just manually use ccall to malloc and free memory as needed I guess.

There's a nice talk here [1] about using julia in soft-real-time for robotics.

I'd say the biggest impediment to real-time programming in julia currently is not the GC, but instead that the language has lots of optional optimizations that the compiler can choose to not perform if it thinks it'd be beneficial to do so, and those optimizations can change between minor versions. Hence, there's a lot of testing you'd need to do to make sure your code really is doing exactly what you want and you won't hit the GC or dynamic dispatch, and you'll need to redo that testing every time you update julia or any packages, which is obviously not ideal.

[1] https://www.youtube.com/watch?v=dmWQtI3DFFo

Re:From Julia to Rust by duped on Sunday June 06, 2021 @09:16AM (#27412949)
That's not strictly true - if the GC requires all threads to pause ("stop the world") then there is no effective way to write code that isn't impacted without adding the soft real-time requirement and workarounds to every part of the program.

Essentially most GCs require you to pay for it even if you don't use it in critical sections. I'm not sure if Julia has the semantics for explicitly disabling GC during hot loops, but it is extremely difficult to do that without the dedicate hooks.

The issue is not performance - GCs are usually faster than not. It's determinism.

Re:From Julia to Rust by cbkeller on Sunday June 06, 2021 @12:08PM (#27414058)
You can just turn off the GC with `Base.GC.enable(false)`
Re:From Julia to Rust by duped on Sunday June 06, 2021 @04:56PM (#27416247)
does that disable it globally or just in critical sections?
Re:From Julia to Rust by eigenspace on Sunday June 06, 2021 @05:46PM (#27416642)
Globally. Is your concern about running latency insensitive code concurrently with running latency sensitive code?

For that, I'd probably just do multiprocessing and have one realtime process that only operates on preallocated or stack allocated buffers with the GC disabled, and then another separate process that does your latency insensitive stuff.

I'm sure other languages are able to handle this sort of thing more elegantly in one process with tasks, but it can be made to work in julia, and there are lots of situations like robotics where you may want to leverage Julia's excellent ecosystem in a realtime system, so I just disagree with your earlier statement about julia being incompatible with the application domain, even if it's not a perfect language for it.

Re:From Julia to Rust by eigenspace on Saturday June 05, 2021 @08:46PM (#27409679)
Okay, reading my earlier comment I said "perfectly find for soft realtime", that's maybe a bit too far. Let's go with "okay for soft realtime" instead.
Re:From Julia to Rust by cbkeller on Sunday June 06, 2021 @09:39PM (#27418092)
Honestly you were probably fine the first time, given that you can easily disable the GC, and that it's really not that hard to entirely avoid generating garbage (in which case the GC generally wouldn't have been triggered even if you'd left it on).
Re:From Julia to Rust by eigenspace on Sunday June 06, 2021 @11:00PM (#27418551)
I think the lack of solid guarantees for optimizations is enough to downgrade it a little past "perfectly fine"
Re:From Julia to Rust by miguelraz on Saturday June 05, 2021 @06:51PM (#27409078)
Yes, to explore those differences was the point of the post.