Text search library in Rust, day 2
I’m rewriting my GNETextSearch C library in Rust.
The plan
Taking some inspiration from Michael Bryan’s article “How to not RiiR”, my plan is to first write a Rust wrapper around GNETextSearch
. I’ll try to write an ergonomic Rust API for the library, add a bunch of tests, and then start replacing the C implementation with a Rust one.
Creating a Rust workspace
Before getting started on any code, of course, I needed to create a new Rust project. After creating a new GitHub repository at atdrendel/text-search and cloning it, I followed these instructions to create a Rust workspace. Inside of the workspace, I created two library crates (text-search
and text-search-sys
) to host, respectively, the Rust text search library and the old C one.
$ cargo new text-search --lib
$ cargo new text-search-sys --lib
# Cargo.toml in the root of the workspace
[workspace]
members = [
"text-search",
"text-search-sys",
]
Configuring text-search-sys to build GNETextSearch
My first task was to get GNETextSearch
building inside of text-search-sys
. I wanted to consume GNETextSearch
directly, which meant adding it to the text-search-sys
crate. Not being a fan of git submodules, I elected to go with git subtree.
# inside of the workspace's root folder
$ git subtree add --prefix text-search-sys/vendor/GNETextSearch git@github.com:atdrendel/GNETextSearch.git master --squash
Since GNETextSearch is a small library without any dependencies, it was pretty easy to get to build. Apparently, the preferred way to include C dependencies in Rust projects is to use the cc-rs crate to build them. I added cc-rs
to text-search-sys
’s list of build dependencies by modifying its Cargo.toml
file:
# Cargo.toml in text-search-sys
[package]
name = "text-search-sys"
version = "0.1.0"
authors = ["Anthony Drendel"]
edition = "2018"
links = "GNETextSearch"
description = "Raw bindings to the GNETextSearch C library"
license = "BSD-2-Clause"
build = "build.rs"
[dependencies]
[build-dependencies]
cc = "1.0"
The Rust file designated by the build command (relative to the package root) will be compiled and invoked before anything else is compiled in the package, allowing your Rust code to depend on the built or generated artifacts.
So, given that, the next thing I did was to create a build.rs
file in the text-search-sys
folder. After a bit of trial and error, I ended up with the following implementation for build.rs
.
use std::{env, path::PathBuf};
fn main() {
let project_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap())
.canonicalize()
.unwrap();
let src = project_dir
.join("vendor")
.join("GNETextSearch")
.join("GNETextSearch");
build_gne_text_search(&src);
}
fn build_gne_text_search(src: &PathBuf) {
cc::Build::new()
.file(src.join("Set/countedset.c"))
.file(src.join("String/stringbuf.c"))
.file(src.join("Tree/ternarytree.c"))
.file(src.join("UTF-8/tokenize.c"))
.include(&src)
.include(src.join("Set"))
.include(src.join("String"))
.compile("GNETextSearch");
}
I had to specify each C file to compile and then include all of the folders containing required header files. After this, running cargo build
in the root folder of the workspace succeeded.
Next, I’ll need to generate the Rust bindings for GNETextSearch
’s C interface.