How to link gtk from Rust? - rust

I'm using the GTK provided Rust example from https://www.gtk.org, and am wondering how to nail down GTK as a dependency (or other type of prerequisite) so that the Rust program given there will build with cargo.
Here's the code, copied from there without modification:
use gio::prelude::*;
use glib::clone;
use gtk::prelude::*;
// When the application is launched…
fn on_activate(application: &gtk::Application) {
// … create a new window …
let window = gtk::ApplicationWindow::new(application);
// … with a button in it …
let button = gtk::Button::new_with_label("Hello World!");
// … which closes the window when clicked
button.connect_clicked(clone!(#weak window => move |_| window.close()));
window.add(&button);
window.show_all();
}
fn main() {
// Create a new application
let app = gtk::Application::new(Some("com.github.gtk-rs.examples.basic"), Default::default())
.expect("Initialization failed...");
app.connect_activate(|app| on_activate(app));
// Run the application
app.run(&std::env::args().collect::<Vec<_>>());
}
As is, without any Cargo.toml definitions for gtk, gtk symbols will not resolve when compiling the program.
How would you go about it most idiomatically?
I do have GTK installed on my system of course.
Thanks!

Rust can call code using the C ABI, but as #Herohtar explains, it is not automatic. You need to give the compiler the information about the extern "C" functions to be able to call them.
For commonplace libraries, there may be existing bindings out there. These are Rust library crates that contain all the glue already written for you. They work like any other Rust dependency that you put in your Cargo.toml file.

So indeed I ended up following the suggested approach of using the gtk-rs rust integration project rather than pursue any gtk wrapper of my own. My initial source of confusion has been that the gtk website just gave the gtk-rs sample code without any reference or mention of the gtk-rs project being assumed.
The public crate which that code assumes, even though not explicitly stated in the original code sample page, is called gtk; I guess that's the default naming convention: that the namespace and crate name are typically the same.
Including that crate in the build indeed satisfies the gtk namespace appearing in the code. And the same trivial naming convention applies for the gio and glib namespace requirements appearing in the sample code. They all seem to come from the gtk-rs project, though unlike other programming languages, this is not explicitly evident in the crate specification as only the name of the create, not the project containing it, are specified in cargo.
As to versions, I have chosen the most recent version of the gtk crate (as per suggestion from my VSCode rust plugin) and the newest crate feature providing support for GTK's latest supported API.
And I have also arbitrarily plucked versions of the gio and glib crates off another gtk-rs project hoping for seamless interoperability between the last two and the gtk crate itself, as they seem to need to be separately specified and not brought in by the gtk crate itself.
So with the following dependency definitions in my Cargo.toml, the sample code works:
[dependencies]
gtk = { version = "0.9.2", features = ["v3_22"] }
glib = "0.10"
gio = { version = "0.9", features = ["v2_56"] }
I'm new to rust but I might at present time think some of this is more complicated than it could be or would be in the case of other rust projects, or, that using the sample code assumes familiarity with the compatibility scheme of gtk-rs crates or that of the underlying native compatibility of gtk with glib and gio versions!
The latter aspect might be simple enough but not explicitly discussed where gtk-rs documentation discusses versioning.
An odd sticking point for me was that the following method had to be replaced by me with the one following it, perhaps this code sample is just slightly outdated compared to the latest version of the crate (?!).
provided code sample:
gtk::Button::new_with_label
actual method that is defined:
gtk::Button::with_label

Related

How is it possible to keep Rust module documentation in separate Markdown files?

This section of the Rust book seems to imply that it is possible to keep Rust documentation in separate .md files, but it does not say how these .md files can then be included back. How does this work?
The syntax for putting Rust module documentation in separate Markdown files is:
#![doc = include_str!("path/to/some-documentation.md")]
/* content of the module */
This is supported since stable Rust 1.54.0.
On old nightly compilers from 1.50.0-nightly through 1.53.0-nightly, an unstable feature is required in order for the above to be available.
#![feature(extended_key_value_attributes)]
#![doc = include_str!("path/to/some-documentation.md")]
On nightly compilers 1.24.0-nightly through 1.53.0-nightly, the following alternative syntax is available, but has since been removed.
#![feature(external_doc)]
#![doc(include = "path/to/some-documentation.md")]
It doesn't. That section on describing the functionality of rustdoc is saying that it can process individual .md files. The third paragraph touches on this:
Documentation can be generated in two ways: from source code, and from standalone Markdown files.
Insofar as I am aware, there is no extant way to put code documentation in external files. It would be theoretically possible to do so using a procedural derive macro, but I'm not aware of any crate that actually does this.
In stable Rust, you can mimic the unstable external-doc feature through clever macros.
An easy way to do this is to use the doc-comment crate:
#[macro_use]
extern crate doc_comment;
// If you want to test examples in your README file.
doctest!("../README.md");
// If you want to document an item:
doc_comment!(include_str!("another_file.md"), pub struct Foo {});
You can see a complicated version of this in my crate SNAFU, where I use it for the user's guide.
The "by-hand" version involves passing the thing to be documented along with the included markdown:
macro_rules! inner {
($text:expr, $($rest: tt)*) => {
#[doc = $text]
$($rest)*
};
}
inner! {
include_str!("/etc/hosts"),
mod dummy {}
}
See also:
Is it possible to emit Rust attributes from within macros?
How to embed a Rust macro variable into documentation?
Generating documentation in macros

How to skip a library crate's examples depending on the target platform?

I have a Rust library crate which I only use on Linux, but I don't know of any reason it shouldn't work on Windows; however one of the examples is Unix-specific, so it the crate fails the build on AppVeyor.
The reason is that there's a Unix-only dependency (Termion) used by one of the examples, so when I used an AppVeyor template, it fails to build that dev-dependency.
So I made the dependency conditional:
[target.'cfg(unix)'.dev-dependencies]
termion = "1.0"
So far so good, but of course now that example fails on extern crate termion.
What I need is to just not build that example on non-Unix-like targets. I was hoping something like:
[[target.'cfg(unix)'.example]]
name = "foo"
would work, but I get:
warning: unused manifest key: target.cfg(unix).example
error: no example target named `foo`
Another promising way forward was Cargo's required-features, but as of writing it's apparently not in stable Cargo, which means that doesn't help me checking that it works on stable Rust on Windows.
The last option I can think of is to #[cfg(unix)] away most of the example's source, turning it into a stub on Windows, but I'd really like a cleaner way of doing it.
Is there some existing way of skipping an example on some unsupported targets that works with current stable Rust/Cargo?

How to build a standard Linux .so library with stable Rust without using std?

Cargo.toml:
[package]
name = "proba"
version = "0.1.0"
[lib]
name = "mycoollib"
path = "src/mycoollib.rs"
crate-type = ["cdylib"]
src/mycoollib.rs:
#![no_std]
fn func(v: i32) -> i32 {
v + 10
}
When I try to run cargo build:
error: language item required, but not found: `panic_fmt`
error: language item required, but not found: `eh_personality`
A nightly build is needed to implement panic_fmt and eh_personality, but "Using Rust Without the Standard Library" of the Rust book says that libs can build on stable.
I believe that the documentation is misleading in this case. The problem is in the definition of the word "library".
A Rust library (sometimes known as an rlib) can use #[no_std] and not require definitions for panic_fmt or eh_personality. That's because eventually it will be linked against into a binary that does use the standard library, which defines these symbols.
A native library (either dylib, cdylib, or probably staticlib) has no guarantee of being linked with these symbols / language items, so they must be defined up front.
As you can see, both of these could be called a "library". I think that the book is being a little loose with the terminology, leading to the confusion.

How to add code only for specific Rust version without using a build script?

I use a method that appeared in Rust 1.10 for my tests but I want my crate to also work with version 1.7.
Is there a way (something like attribute #[cfg(min_version="1.10")]) to specify code that should only run in Rust 1.10 or newer?
I could use a build script, but I don't want a more complicated build just because I wanted to test my crate on an older Rust version.
While there is no way except build scripts (and in the future procedural macros) to check compiler versions, you can use feature flags to manually enable and disable code.
Usually you want to use some new compiler feature to provide new functionality which you could not do with the old compiler. In that case you use the cfg attribute with feature flags which you define to enable code. E.g.
#[cfg(feature = "foo")]
pub fn foo() {
cool_new_compiler_function();
}
And in your Cargo.toml:
[features]
foo = []
Hiding code behind feature flags like this also works in test code. In your specific case you could alternately introduce a legacy feature and disable tests using modern code like this:
#[test]
#[cfg(not(feature = "legacy"))]
fn test_foo() {
Foo::foo();
}
And then to run tests in legacy mode you run:
cargo test --features "legacy"
Note that doing it the latter way means that your tests will not compile by default on older compilers. For normal (non-test) code, adding such a legacy flag would be a very bad, breaking change.

Issue when replacing a crates dependency with a local version

So I am trying to serialize a struct using bincode following these instructions, and I was able to get that to work.
But then I wanted to serialize a struct with an IpAddr enum. Since IpAddr doesn't implement the Encodable trait needed, I downloaded the rustc_serialize crate from git and implemented encodable for IpAddr myself. I then changed my Cargo.toml file to:
[dependencies]
# rustc-serialize = "0.3"
byteorder = "0.3"
bincode = "0.4"
[dependencies.rustc-serialize]
path = "src/rustc-serialize-master"
But now, the same code from the struct I was using doesn't compile saying that
rustc_serialize::serialize::Encodable is not implemented for my struct even though i have #[derive(RustcEncodable)] above the struct.
Even when I get rid of the code I added to the local version of rustc_serialize, I still get that error.
I think it might be due to something being screwed up with the way bincode and a local rustc_serialize interact, but I'm not sure.
Please review the Cargo documentation on overriding dependencies:
To specify overrides, create a .cargo/config file in some ancestor of your project's directory (common places to put it is in the root of your code directory or in your home directory).
Inside that file, put this:
paths = ["/path/to/project/rand"]
Going deeper, you are likely running into issue 22750 - two different versions of a crate interacting leads to unhelpful error messages. When you add rustc-serialize to your dependencies, you aren't replacing the old version, you are adding a new one.
In general, this is a good feature. If my project relies on crates A and B and they both rely on crate Z but with different versions, Rust can handle that just fine. The problem arises when they re-export items from those crates.

Resources