• 0 Posts
  • 14 Comments
Joined 2 years ago
cake
Cake day: June 15th, 2023

help-circle


  • Visual Studio Code with rust-analyzer has all the features I would expect from an IDE. I mean, rust-analyzer works together with cargo, so refactoring over file boundaries is not an issue. Visual Studio Code has built-in support for debugging and source control…

    That said, I am currently trying to change my workflow to use vim instead of Visual Studio Code, due to my laptop’s small screen size. Rust-analyzer works great in vim too, but I still need to tweak a few things, like how warnings from cargo check are being displayed…


  • I’m mostly using Rust for a spare time Visual Novel Engine (and Visual Novel) project.

    I picked Rust, because I wanted to do something productive with my higher-free-macro crate (which is a tech-demo, but hey, if I have written it, I can just as well use it for something). If you want to get an idea how scripting the VNs in that engine will work, check out the “text adventure” example in higher-free-macro. However, Rust is definitely not an ideal choice for this project. Since performance usually isn’t a concern for visual novels, a higher-level, pure functional language like Haskell or Lean4 would probably have been a better option.

    Apart from that I’m using it for many smaller things. For instance I’ve written a small tool for my status bar, swaystatus. (I was not aware that i3status-rust exists when I started working on it, and now I am already committed.) Here I chose Rust mainly because I wanted to learn about Foreign Function Interface in Rust. While I didn’t upload the sources to github until recently, I mostly had been working on this tool several years ago, when I still was a Rust newbie. However, I got back to this project some weeks ago, when I realized that I would like to have an ALSA volume display, which is now in a WIP state on a separte branch.

    I’m also using Rust for some out-of-tree prototypes at work. In this case the main reason for choosing Rust is development speed. I’m using Iced.rs to build those prototype GUIs, and Iced is an amazing toolkit. Making a prototype with it is shockingly fast. If I were to do something similar with basically any other GUI toolkit, it would take me significantly longer.

    And last, but not least: I’ve published a free app for SailfishOS which is compatible with passwordmaker.org: Passfish, and its underlying library, passwordmaker-rs. Here I chose Rust, because it’s way less error prone than C++ (and let’s better not talk about QML JavaScript). Also, I wanted to show that using Rust for SailfishOS app development is viable, and that it’s actually a quite pleasant experience. (If you want to try passfish, builds are available via the official SailfishOS store, or on OpenRepos).



  • I didn’t look beyond the main parts of the HTTP moduel, but what I’ve noticed basically immediately was that your pub fn start_server(address: &str, request_handler: fn(Request) -> Response) -> io::Result<()> uses a function pointer parameter.

    This is overly restrictive. fn a()->b only accepts fns, and closures that do not capture their environment (see the book’s chapter on closures). In order to accept closures that capture their environment, you could make that function generic: pub fn start_server(address: &str, request_handler: F) -> io::Result<()> where F : Fn(Request)->Response + Clone +'static.

    • The Clone requirement is necessary, because you need to pass a copy of the handler to each spawned thread.
    • The 'static lifetime is needed because you can’t guarantee that the thread doesn’t outlive the call to start_server.

    Now, this can be improved further, because Rust offers a tool to guarantee that all threads are joined before run_server returns: Scoped Threads.

    • Scoped Threads make the 'static requirement unnecessary, because the function that contains them outlives them by definition.
    • In addition, the Clone requirement is not needed either, because due to the limited lifetimes, you can take request_handler by reference, and all references are Copy (which is a subtrait of Clone).

    With scoped threads, a version of your function that could be generic over the request_handler could look something like this (I haven’t tried to compile this, it might be utterly wrong):

    pub fn start_server(address: &str, request_handler: &F) -> io::Result&<()> where F : Fn(Request) -> Response {
        let listener = TcpListener::bind(address)?;
        std::thread::scope(move |s| -> io::Result<()>{
            for stream in listener.incoming() {
                let stream = stream?; // I think ? works here too
                s.spawn(move || {
                    handle_connection(stream, &request_handler);
                });
            };
            Ok(())
        })
    }
    

    Edit: Sorry for the messed up characters. & should of course just be the ampersand character, and < should just be the less-than character.



  • Before you get overly excited, we plan to introduce it later this year. As in game-dev “plan”, as in “it might be cut or delayed” 😜. What is holding us back is that we need time to get a Rust toolchain set up for all our target platforms, which have certain requirements that the toolchain needs to meet, and time is always a tight resource in game dev.

    That said: Our technical director is very adamant at pushing us towards a more functional programming style (his website explains why). If we could, we would go pure functional right now, but it’s really hard to find people who have experience with fully functional languages, and therefore we want to have the next-best thing, which is Rust. (Or F# for Unity projects. We don’t have any Unity projects right now, but we already have used F# in Rescue HQ, for instance.)

    And finally, to answer your questions: I work at stillalive studios. Here is a list of our open positions: https://stillalive.games/careers/ Also I can say from personal experience, that the “speculative application” paragraph is definitely true.







  • I can only speak out of my own experience, which is mostly C++, C#, C and Rust, but I also know a bit of Haskell, Java, Fortran, PHP, Visual Basic, and, to my deepest regret, also JavaScript.

    For additional context: I have been working in game development for the last 7 years, my main language is C++ for Unreal, but I’ve also worked on some Unity projects with C# as main language. Before I switched to game dev I worked in material science, and used C, mostly. I use Rust for my spare time projects, and the game company I work at is planning to introduce it into our Unreal projects some point later this year.

    Of all the languages I mentioned above, (Safe) Rust and Haskell are the only ones that have not yet made me scream at my PC, or hit my head against the desk.

    So, some of the reasons why I personally love Rust:

    • Rust is extremely simple compared to the other languages I mentioned above. If you read the official introduction you know all you need to write Safe Rust code.
    • Rust’s syntax is elegant. It’s not as elegant as Haskell, but it’s a lot more elegant than any C-based language.
    • Rust is (mostly) type safe. There are (nearly) no implicit conversions.
    • Rust is memory-safe, without the runtime overhead that garbage collected languages incur.
      • This is a bit of a neutral point though. The Rust compiler will complain if you make mistakes in memory management. Unlike in managed languages, you still need to do the memory management by hand, and find a working solution for it.
    • The memory management model of Rust (“borrow checker”) makes data dependencies explicit. This automatically leads to better architecture that reflects dependencies, because if the architecture doesn’t match them, development will become an uphill battle against the borrow checker.
    • Due to the borrow checker, you can use references extensively, and rely on the referenced object to valid, and also that it is up-to-date (because it cannot be muted or go out of scope as long as you hold the reference).
    • Traits are an amazing way to abstract over types. Either at zero-cost (static dispatch), or, in the rare cases where it’s needed, using virtual function tables.
    • Rust aims to have no undefined behaviour. If it compiles the behaviour of the code is well defined.
      • This, together with the borrow checker, ensures that there are (nearly) no “weird bugs”. Where in C++ one quite regularly hits issues that at first glimpse seem impossible, and only can be explained after several days of research on cppreference (“oh, so the C++ standard says that if this piece of code gets compiled on a full moon on a computer with a blue power LED, it’s undefined behaviour”), that almost never happens in Rust.
    • Macros in Rust are amazing. There are macros-by-example that work by pattern-matching, but there are also procedural macros, which are Rust functions that take Rust code as input, and generate Rust code as output. This gives you amazing power, and one of the most impressive examples is the Serde serialization framework, that allows you to add serialization to your data types simply by adding an attribute.
    • Tooling for Rust is pretty good. The Rust compiler is well known for its helpful error messages. The rust-analyzer plugin for Visual Studio Code is great too. (It also works with vim, Qt Creator and others, but the but Visual Studio Code works best imho.)

    The points mentioned above mostly apply to Safe Rust though. Unsafe Rust is a different story.

    This brings us to the downsides. Rust isn’t perfect. Far from it, actually. Here are some of the things that aren’t great about Rust.

    • No Higher Kinded Types. This is my main issue with Rust. Even C++ has them (as usual for C++ in a horrible un-ergonomic and utterly confusing way). If Rust had Higher Kinded Types, the language could have been simpler still. For instance, there would have been no need for the async keyword in the language itself.
    • Unsafe Rust is hard. In my opinion even harder than C++, because of Rust’s aliasing rules. Unlike C++, Rust doesn’t allow mutable memory aliasing. That’s because mutable aliasing can never happen in Safe Rust, and not supporting it improves performance. This means that when writing Unsafe Rust, one has to be careful about aliasing.
      • Luckily one only rarely needs Unsafe Rust, usually only in order to call functions from other languages. Still, it’s hard, and I’d generally suggest to use an automated code generator like cxx.rs for interfacing with other languages.
    • Interior Mutability. I understand why it exists, but it breaks a lot of the guarantees that make Rust a great language. So, my conclusion is that one should avoid it as much as possible.

    However, the upsides clearly outweigh the downsides imho.

    tl;dr If a (Safe) Rust program compiles, chances are pretty high that it also works. This makes programming with it quite enjoyable.


  • Another option that the author didn’t mention: Newtypes.

    There’s a pretty neat example for this in Rust By Example: https://doc.rust-lang.org/rust-by-example/generics/new_types.html

    If you don’t factor out the instantiation of the parameters, you get pretty close to having named parameters, actually:

    struct Weeks(u32);
    struct Days(u32);
    
    fn add_weeks_to_days(weeks : Weeks, days: Days) -> Days {
        let w = weeks.0;
        let d = days.0;
        Days(7*w+d)
    }
    
    fn main(){
        let Days(r) = add_weeks_to_days(Weeks(5), Days(3));
        println!("5 weeks and 3 days equals {r} days.");
    }
    

    (Playground)

    It is a little bit more work when writing the API, and the caller has to type a tiny bit extra when calling the function, but it also removes all ambiguity regarding the order of the funtion parameters. If also used on the return type, we suddenly get “named” return values too.