Rust: Add an Example Program to your Library Crate

Recently I decided that I might actually publish my little rust crate. To that end I started looking into some of the things that are needed and what can be done. I have published a perl module in the past. And as a rule for myself, I like to provide an example program that uses a module or library. That is what I will look at today.

When I started this project to learn rust, I was simply making a single program. As things evolved, it became more complex and turned in to a program that uses a library. This was all fine.

But as I looked at making this a library crate. I noticed when I compiled my library it was also bringing in external crates that my library didn’t need. Why? Because my crate was a library and an binary.

Cargo.toml

Old

[package]
-- snip --
edition = "2018"

[dependencies]
trust-dns-resolver = "0.20.1"
ipnetwork = "0.17.0"
regex = "1"

In the old toml file we can see that all of my dependencies are listed together. Even though my library only really makes use of ipnetwork and regex. At this point my regex was not using the lazy_static crate.

I also have not listed any [lib] section.

New

[lib]
name = "redacted" (at least until I publish it)
path = "src/lib.rs"

[[example]]
name = "trust-dns-demo"
path = "examples/trust-dns-resolver.rs"

[dependencies]
ipnetwork = "0.17.0"
regex = "1"
lazy_static = "1.4.0"

[dev-dependencies]
trust-dns-resolver = "0.20.1"

In this new toml file. I have moved the trust-dns-resolver down under [dev-dependencies]. In this way when I compile my library the trust-* crate and any of its dependencies will not be compiled. This has an added benefit that when docs are created. Only items under [dependencies] are included in the docs.

Though you can also run:

cargo doc --no-deps

This will prevent docs from generating docs for dependencies.

Crate Directory Structure

Old

├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── README.md
└── src
    ├── lib.rs
    ├── main.rs
    └── spf

The old setup has a main.rs. This in effect tells cargo that this a binary crate. The [lib] in Cargo.toml also tells it that it is library crate.

The affect of having a main.rs is that both a binary and library are compiled. In my case main.rs has a dependency on trust-dns-resolver.

To make this compile as only a library and also provide an executable example program, the solution is to define an example target.

As seen above.

[[example]]
name = "trust-dns-demo"
path = "examples/trust-dns-resolver.rs"

This defines a new target that can be compiled. Notes from The Cargo Book specifically, Example states.

Files located under the examples directory are example uses of the functionality provided by the library. When compiled, they are placed in the target/debug/examples directory.

Examples can use the public API of the package’s library. They are also linked with the [dependencies] and [dev-dependencies] defined in Cargo.toml.

New

.
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── README.md
├── examples
│   └── trust-dns-resolver.rs
└── src
    ├── lib.rs
    └── spf

What I did here was to create an examples directory, rename and move the main.rs to this new directory as trust-dns-demo.rs

The result

When I run a build. I only use the main dependencies.

$ cargo build
  Compiling memchr v2.3.4
  Compiling serde v1.0.125
  Compiling regex-syntax v0.6.23
  Compiling lazy_static v1.4.0
  Compiling aho-corasick v0.7.15
  Compiling regex v1.4.6
  Compiling ipnetwork v0.17.0
  Compiling something v0.1.0 (/Users/me/Documents/Development/rust/something/

However, when I run a test.

cargo test
   Compiling libc v0.2.96
   Compiling cfg-if v1.0.0
   Compiling proc-macro2 v1.0.27
   Compiling unicode-xid v0.2.2
   -- snip 40 items for brevity --
   Compiling idna v0.2.3
   Compiling rand v0.8.3
   Compiling url v2.2.2
   Compiling thiserror-impl v1.0.25
   Compiling enum-as-inner v0.3.3
   Compiling thiserror v1.0.25
   Compiling trust-dns-proto v0.20.3
   Compiling trust-dns-resolver v0.20.3
   Compiling something v0.1.0 (/Users/me/Documents/Development/rust/something)
    Finished test [unoptimized + debuginfo] target(s) in 21.68s
     Running unittests (target/debug/deps/something-49ff56fbf5edcf40)

running 69 tests
test spf::kinds::test_kind_ip4 ... ok
test spf::kinds::test_kind_exists ... ok
test spf::kinds::test_kind_a ... ok
-- Snip 60 items for brevity --
test spf::tests::spf::test_spf::test_redirect ... ok
test spf::tests::spf::test_spf::test_netblocks2_google_com ... ok
test spf::tests::spf::ptr::capture::test_match_on_ptr ... ok
test spf::tests::spf::ptr::parse::test_exist ... ok
test spf::tests::spf::ptr::parse::test_exist_colon ... ok
test spf::tests::spf::ptr::capture::test_match_on_ptr_colon ... ok

test result: ok. 69 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s

   Doc-tests something

running 5 tests
test src/spf/mod.rs - spf::Spf::from_str (line 66) ... ok
test src/spf/kinds.rs - spf::kinds::MechanismKind::as_str (line 82) ... ok
test src/spf/mechanism.rs - spf::mechanism::Mechanism<IpNetwork>::raw (line 155) ... ok
test src/spf/mechanism.rs - spf::mechanism::Mechanism<String>::new_a (line 65) ... ok
test src/spf/mechanism.rs - spf::mechanism::Mechanism<IpNetwork>::string (line 171) ... ok

test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.64s

Note the inclusion of:

Compiling trust-dns-proto v0.20.3
Compiling trust-dns-resolver v0.20.3

Making the Example program Run

This can be done pretty simply.

$ cargo run --example
error: "--example" takes one argument.
Available examples:
    trust-dns-demo

So we can simply run

$ cargo run --example trust-dns-demo
Finished dev [unoptimized + debuginfo] target(s) in 0.21s
 Running `target/debug/examples/trust-dns-demo`
List of TXT records found for gmail.com.
TXT Record 1:
v=spf1 redirect=_spf.google.com
TXT Record 2:
globalsign-smime-dv=CDYX+XFHUw2wml6/Gb8+59BsH31KzUr6c1l2BPvqKX8=

Decontructing SPF Record
Spf { source: "v=spf1 redirect=_spf.google.com", version: "v=spf1", from_src: true, include: None, redirect: Some(Mechanism { kind: Redirect, qualifier: Pass, mechanism: "_spf.google.com" }), is_redirected: true, a: None, mx: None, ip4: None, ip6: None, ptr: None, exists: None, all: None }
SPF1: v=spf1 redirect=_spf.google.com


Is a redirect: true

redirect: _spf.google.com
mechanism: redirect=_spf.google.com

Closing

That’s it for this article. I hope it is a fairly understandable way to provide an example program along with your rust library.

rust  crate  lib 

See also