Rust and QML on Xubuntu

I just spotted A simple Rust GUI with QML on r/rust and wanted to give it a try. I already had Rust installed, but not QML. My desktop computer is currently running Xubuntu, which is Ubuntu with Xfce. Xfce is based on GTK+, whereas QML is based on Qt. There is a Kubuntu, which is Ubuntu with KDE. Since KDE is based on Qt, it probably has all the stuff we need for QML already. But we needn’t switch to Kubuntu to install QML. We can install it on Xubuntu or any version of Ubuntu just fine 1.

To install QML, I ran

sudo apt install qtdeclarative5-dev qml-module-qtquick-controls

which installed a whole bunch of dependencies, since I had no Qt things at all on this box yet. This got “Hello, World” to work, but there was not enough whitespace.

Screen shot of "hello world" in QML

Figure 1: “hello world” in QML!

I added width and height attributes to the .qml file and changed anchors.fill to anchors.centerIn

ApplicationWindow {
    visible: true
    width: 200
    height: 100
    Text {
        anchors.centerIn: parent
        text: message
    }
}

This looked a little better.

Screen shot of "hello world" with whitespace added

Figure 2: “hello world” with whitespace added

I probably should have made the font bigger as well, but I didn’t want to go too far down the “pointy-clicky nonsense” rabbit hole.

Later in the exercise, I needed QtQuick.Dialogs as well. For that, I ran

sudo apt install qml-module-qtquick-dialogs

Anyway, it’s a fun little blog post…you should give it a try! Bottom line is, Ubuntu has everything we need to follow along with it, even if the names aren’t that obvious.

Footnotes:

1

Indeed, renaming the distribution after the window manager like this is my least favorite thing about Ubuntu. I appreciate easy access to Xfce, but I didn’t need a whole new name. Xubuntu, Kubuntu, Lubuntu,…gah!

Advertisements
Rust and QML on Xubuntu

Strings in Go and Rust

This week at Go Meetup, we talked briefly about how strings in Go are UTF-8, but not really. What I mean is, on the one hand, we can write

s := "Hello, 世界!"
fmt.Println(s)

and it prints out

Hello, 世界!

as expected. But on the other hand, we can put an invalid UTF-8 sequence into a string as well

s := "\x67\x72\xfc\xdf\x65"

It will compile just fine, but print out junk.

gr��e

If we accept strings from an external source, we probably don’t want to do stringy things with them without first checking that they’re valid. For example, this code

package main

import (
    "fmt"
    "os"
)

func main() {
    for _, s := range os.Args {
        fmt.Println(s)
    }
}

just prints whatever we give it

$ ./garbage foo bär $(echo -en "\x67\x72\xfc\xdf\x65") baz
./garbage
foo
bär
gr��e
baz

while this one

package main

import (
    "fmt"
    "os"
    "unicode/utf8"
)

func main() {
    for _, s := range os.Args {
        if utf8.ValidString(s) {
            fmt.Println(s)
        } else {
            fmt.Println("not valid")
        }
    }
}

only prints valid strings

$ go build valid_string.go 
$ ./valid_string foo bär $(echo -en "\x67\x72\xfc\xdf\x65") baz
./valid_string
foo
bär
not valid
baz

In Rust, strings are UTF-8 as well. We can write

let s = "Hello, 世界!";
println!("{}", s);

and it prints out

Hello, 世界!

as expected. But unlike Go, we can’t put an invalid UTF-8 sequence in a string. This

let s = "\x67\x72\xfc\xdf\x65";

doesn’t even compile

error: this form of character escape may only be used with characters in the range [\x00-\x7f]

However, we still need to be careful. This

let v = vec![0x67, 0x72, 0xfc, 0xdf, 0x65];
let t = String::from_utf8(v);
println!("{:?}", t);

compiles fine, but gives a run-time error

Err(FromUtf8Error { bytes: [103, 114, 252, 223, 101], error: Utf8Error { valid_up_to: 2 } })

So once again, if we accept strings from an external source, we probably don’t want to do stringy things with them without first checking that they’re valid. But, unlike in Go, we can’t even put them in a string until we check. This code

use std::env;

fn main() {
    for arg in env::args() {
        println!("{}", arg);
    }
}

panics if any arguments are not valid UTF-8

$ ./valid_string_panic foo bär $(echo -en "\x67\x72\xfc\xdf\x65") baz
./valid_string_panic
foo
bär
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: "gr��e"', ../src/libcore/result.rs:837
note: Run with `RUST_BACKTRACE=1` for a backtrace.

Instead of std::env::args, we can use std::env::args_os to collect the arguments

use std::env;

fn main() {
    for arg in env::args_os() {
        println!("{:?}", arg);

        //println!("{}", arg);
        // does not compile
    }
}

This gives us an OsString instead of a String. Right away, we can see it’s different because it won’t even compile if we try to print it with “{}”. When we change to “{:?}”, we get junk for invalid UTF-8

$ ./valid_string_garbage foo bär $(echo -en "\x67\x72\xfc\xdf\x65") baz
"./valid_string_garbage"
"foo"
"bär"
"gr��e"
"baz"

To check that it’s valid, we can try to convert the OsString to a String. The to_str method returns an Option, which we can check

use std::env;

fn main() {
    for arg in env::args_os() {
        match arg.to_str() {
            Some(s) => println!("{}", s),
            None => println!("not valid"),
        }
    }
}

Thus we get

$ rustc valid_string.rs
$ ./valid_string foo bär $(echo -en "\x67\x72\xfc\xdf\x65") baz
./valid_string
foo
bär
not valid
baz

just as in Go.

So even though both Go and Rust use UTF-8 for strings, they are not the same model. There’s more to it. When it comes to encodings, there’s always more to it!

Strings in Go and Rust

Rusty Perl

I wanted to call Rust from Perl, so I tried to follow along with this blog post which does exactly that. But it was written before the release of Rust 1.0, so not everything still works. Here’s what I did:

Create a new Rust project called points.

$ cargo new points
     Created library `points` project
$ cd points

Add a lib section to the Cargo.toml file to create a .so instead of a .rlib.

[package]
name = "points"
version = "0.1.0"
authors = ["oylenshpeegul <oylenshpeegul@gmail.com>"]

[lib]
name = "points"
crate-type = ["dylib"]

Now edit the src/lib.rs as @pauldwoolcock describes, but there’s no deriving, no box, no int, and abs_sub is deprecated.

#[derive(Copy, Clone)]
pub struct Point { x: i64, y: i64 }

struct Line { p1: Point, p2: Point }

impl Line {
    pub fn length(&self) -> f64 {
        let xdiff = self.p1.x - self.p2.x;
        let ydiff = self.p1.y - self.p2.y;
        ((xdiff.pow(2) + ydiff.pow(2)) as f64).sqrt() 
    }
}

#[no_mangle]
pub extern "C" fn make_point(x: i64, y: i64) -> Box<Point> {
    Box::new( Point { x: x, y: y } )
}

#[no_mangle]
pub extern "C" fn get_distance(p1: &Point, p2: &Point) -> f64 {
    Line { p1: *p1, p2: *p2 }.length()
}

#[cfg(test)]
mod tests {
    use super::{Point, get_distance};

    #[test]
    fn test_get_distance() {
        let p1 = Point { x: 2, y: 2 };
        let p2 = Point { x: 4, y: 4 };
        assert!((get_distance(&p1, &p2) - 2.828427).abs() < 0.01f64);
    }
}

Now try running the tests!

$ cargo test
    Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
     Running target/debug/deps/points-0a1a2813ecad97ba

running 1 test
test tests::test_get_distance ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

If we do a cargo build, we’ll get a debug build and our libpoints.so will be in one location

$ cargo build
    Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs

If we do a cargo build --release, we’ll get a release build and our libpoints.so will be in another location.

$ cargo build --release
    Finished release [optimized] target(s) in 0.0 secs

To use libpoints.so from Perl, we’ll create a perl directory with our points.pl script in it.

$ mkdir perl
$ touch perl/points.pl

We’ll use the FindBin module to link to the libpoints.so file relative to where we are now. And we’ll use a debug flag to link to either the debug or the release.

#!/usr/bin/env perl

use v5.24;
use warnings;
use FindBin;
use FFI::Raw;

my $debug = shift;

my $libpoints = "$FindBin::Bin/../target/release/libpoints.so";
if ($debug) {
    $libpoints = "$FindBin::Bin/../target/debug/deps/libpoints.so";
}

my $make_point = FFI::Raw->new(
    $libpoints,
    'make_point',
    FFI::Raw::ptr,
    FFI::Raw::int, FFI::Raw::int,
);

my $get_distance = FFI::Raw->new(
    $libpoints,
    'get_distance',
    FFI::Raw::double,
    FFI::Raw::ptr, FFI::Raw::ptr,
);

my $p1 = $make_point->call(2,2);
my $p2 = $make_point->call(4,4);

my $result = $get_distance->call($p1, $p2);
say "The distance from (2,2) to (4,4) is $result (the square root of 8).";

Now we should be able to run the Perl script from anywhere, with either the debug build or the release build.

$ perl/points.pl 1
The distance from (2,2) to (4,4) is 2.82842712474619 (the square root of 8).

$ perl/points.pl 
The distance from (2,2) to (4,4) is 2.82842712474619 (the square root of 8).

All of these files are on github.

Rusty Perl