Hacker News new | past | comments | ask | show | jobs | submit login
Todo example in Rust (vandenoever.info)
154 points by Ygg2 on June 9, 2018 | hide | past | favorite | 13 comments



I've read about this project in the past and although it definitely is interesting it's still a bit unclear to me if this is a way to use Qt through Rust or the other way around. Reading through this example it seems like you use QML to structure you UI which is straightforward enough but there are a couple of qml crates which do pretty much the same thing without including an extra binding generation step (or C++ at all).

Searching through the repo [0,1] it seems like you still need to write a certain amount of C++ especially if you're using QtWidgets instead of QML. So in my eyes this seems like a way to incorporate Rust code into a Qt project rather than build a Qt interface for use in Rust, as the end program is essentially a C++ program with some Rust bits.

From what I understand if you have a fair amount of existing Rust code to reuse in a Qt GUI application this is probably the best way to get them to talk together but it's a still a bit far from "Qt GUI for your Rust programs". I get that the distinction between the two is a bit blurry but in any case you still need to write a considerable amount of C++ especially if you opt for the QtWidgets way.

Of course it is entirely possible that I've completely missed the point so in that case please correct me.

[0] https://github.com/KDE/rust-qt-binding-generator/tree/master...

[1] https://github.com/KDE/rust-qt-binding-generator/tree/master...


Rust Qt Binding Generator is for projects that contain Rust and QML or Rust and Qt widgets. The GUI code will be in the native language for Qt: C++ or QML. The data model underneath and any non-UI parts can be written in Rust.

This approach was chosen because it's hard to wrap Qt Widgets (let alone QML) safely in a Rust API. Generating binding code is a very pragmatic approach that works now. You do not need external libraries, just generate some code, include it in your project and use qmake/cmake/... and cargo together.

I've explained this approach in a presentation at FOSDEM [0].

That being said, there might well come a better solution that manages to wrap Qt and the mechanisms of deriving from Qt model classes in a Rust API. It may even manage to require only Cargo as a build tool. In fact, only yesterday Olivier Goffart presented a prototype that does just that. [1] And that project has the exact same to-do application in its examples with only Rust and QML (no JSON or C++).

Switching between those approaches should be very simple though. Especially since Rust has static typing which makes refactoring a breeze.

[0] https://fosdem.org/2018/schedule/event/rust_qt_binding_gener... [1] https://woboq.com/blog/qmetaobject-from-rust.html


What makes Qt hard to bind? Is it just because it is a large C++ project or is there something particularly hard about Qt?


There are quite some challenges. Qt, like most C++ programs can exhibit undefined behavior. In Rust, no code is allowed to have undefined behavior [0]. For safe functions, the compiler checks this. For unsafe functions, the function documentation should state how to call the function to avoid undefined behavior.

Qt is enormous. There are projects that generate bindings automatically. But to know what is safe, you'd have to read the API docs and write the binding by hand. Some parts of Qt might never be safe. For example, loading QML with QQmlApplicationEngine::loadData is not safe because it runs possibly unsafe QML code. So a binding to QQmlApplicationEngine::loadData should be marked unsafe and its documentations should state: 'only load safe QML'.

Most current FFI examples in Rust go via C bindings. There's not a lot of work on wrapping C++ libraries yet (that I'm aware of). C++ is vastly more complex than C. So ensuring safety is harder.

In most Qt projects, there will be places in the code where you have to derive from a Qt class. So the binding should have a way to do that.

The Qt ownership models is hierarchical. If an object has a parent, it will be freed when the parent is freed. A Rust binding should translate that behavior in a safe API.

Signals and slots in Qt are tricky to wrap in a safe API too.

In short, wrapping Qt in a Rust API is a humongous task. Rust Qt Binding Generator takes a pragmatic approach and lets you combine Qt and Rust in one project. It does so by generating a binding around your data model instead of around the entire Qt API.

[0] https://doc.rust-lang.org/reference/behavior-considered-unde...


note that a TodoMVC-like example can be reached in less than 150 lines of pure QML : https://github.com/jcelerier/TodoMVC-QML/blob/master/Main.qm...


Nice! QML is really great for these things.

Author of the blog post here. I've started out the code in the post with a pure QML version of course and then replaced the model with a Rust model since the point of the blog is to show how to write the model in Rust.

I like your solution of using a DelegateModel to show All, Completed or Active items. I went for a different approach:

        RowLayout {
            property bool show: filter.currentIndex === 0
	                || (filter.currentIndex === 1 && !completed)
	                || (filter.currentIndex === 2 && completed)
	    visible: show


cool :)

Are you aware of Olivier Goffart's take on QML in Rust ? https://woboq.com/blog/qmetaobject-from-rust.html ; it seems that it could maybe be used instead of the custome code-gen you did.

That's a great time for Qt and Rust, no doubt :p


Yes, I proofread his blog post and tested the code. :-)

Olivier's approach is very clever. Especially the method of extending Qt classes.


Looks like a good starter Rust project. I am learning Rust and every bit of such blog helps. Just curious to see a .. syntax here: (row..row + count). What does it mean?


Dot dot is range notation: https://doc.rust-lang.org/rust-by-example/flow_control/for.h...

Here's the reference for the drain() method on vectors which takes the range argument: https://doc.rust-lang.org/rust-by-example/flow_control/for.h...

Altogether, it sounds like (I haven't read the whole thing) it's supposed to drain from the current row until the end, including every row in between.


A simpler example might be slicing a list:

    let strings = ["foo", "bar", "baz"];
    let slice = &strings[0..2];
    // prints "foo" and "bar"
    for s in slice {
        println!("{}", s);
    }
Or looping over numbers:

    // prints 0, 1, 2, 3, and 4
    for i in 0..5 {
        println!("{}", i);
    }


I lack the knowledge to judge this on a technical level, although what do I know about Rust and Qt/KDE make me excited. But I can definitely say that's a great logo!


Can somebody tell Alessandro Longo to at least take the official Rust logo. The R is off.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: