Skip to content

Commit

Permalink
Rust: documentation and release pipelines (#130)
Browse files Browse the repository at this point in the history
* Start to clean up Rust docs

* Update CI

* Update release action (needs testing)

* Sketch docs

* Add non-working link

* Clean up some docs, un-mark Rng as send/sync

* Re-mark rng as send+sync

* cargo fmt

* three languages -> four

* Improve rust API docs

* Capitalization fixes

* Consistency edits

* Update ffi.rst

* Typo fixes

* Update README for Rust example

* Add section about STAN_THREADS to rust docs

* Add how to run the example

---------

Co-authored-by: Adrian Seyboldt <adrian.seyboldt@gmail.com>
  • Loading branch information
WardBrian and aseyboldt committed Jun 29, 2023
1 parent 32fac7e commit a184246
Show file tree
Hide file tree
Showing 11 changed files with 205 additions and 67 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -272,5 +272,5 @@ jobs:
run: |
cargo clippy
cargo fmt --check
cargo run --example=make_model
cargo test --verbose
cargo run --example=example
cargo test --verbose
18 changes: 18 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,19 @@ jobs:
- name: Set up Julia
uses: julia-actions/setup-julia@v1

- name: Install LLVM and Clang
uses: KyleMayes/install-llvm-action@v1
with:
version: "15.0"
directory: ${{ runner.temp }}/llvm

- name: Update version numbers
if: ${{ !inputs.is_rerun }}
run: |
sed -i 's/Version:.*/Version: ${{ inputs.new_version }}/' R/DESCRIPTION
sed -i 's/version = .*/version = "${{ inputs.new_version }}"/' julia/Project.toml
sed -i 's/__version__ = .*/__version__ = "${{ inputs.new_version }}"/' python/bridgestan/__version.py
sed -i 's/^version = .*/version = "${{ inputs.new_version }}"/' rust/Cargo.toml
sed -i 's/#define BRIDGESTAN_MAJOR .*/#define BRIDGESTAN_MAJOR '"$(echo ${{ inputs.new_version }} | cut -d. -f1)"'/' src/version.hpp
sed -i 's/#define BRIDGESTAN_MINOR .*/#define BRIDGESTAN_MINOR '"$(echo ${{ inputs.new_version }} | cut -d. -f2)"'/' src/version.hpp
Expand Down Expand Up @@ -103,6 +110,17 @@ jobs:
packages_dir: python/dist/
skip_existing: true

- name: Publish Rust crate
if: ${{ !inputs.dry_run }}
run: |
cd rust/
cargo publish --token ${CRATES_TOKEN}
env:
# clang is necessary unless we want to do --no-verify
LIBCLANG_PATH: ${{ runner.temp }}/llvm/lib
LLVM_CONFIG_PATH: ${{ runner.temp }}/llvm/bin/llvm-config
CRATES_TOKEN: ${{ secrets.CRATES_TOKEN }}

- name: Create JuliaRegistration comment
if: ${{ !inputs.dry_run }}
uses: peter-evans/commit-comment@v2
Expand Down
3 changes: 3 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ We use [Gnu make](https://www.gnu.org/software/make/) for builds. If you have p

* Julia code is formatted using [JuliaFormatter](https://github.com/domluna/JuliaFormatter.jl).

### Rust development

* Rust development is based on `cargo`, which should handle dependencies, testing, and formatting.

## Proposing a new interface language

Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,16 @@ to download the appropriate Stan compiler for your platform into
### Example programs

This repository includes examples of calling Stan through BridgeStan
in Python, Julia, R, and C.
in Python, Julia, R, Rust, and C.

* From Python: [`example.py`](python/example.py)

* From Julia: [`example.jl`](julia/example.jl)

* From R: [`example.r`](R/example.R)

* From Rust: [`example.rs`](rust/examples/example.rs)

* From C: [`example.c`](c-example/example.c)

Examples of other functionality can be found in the `test` folder for each interface.
Expand All @@ -81,3 +83,6 @@ API, which in turn was derived from

Thanks to Sebastian Weber (GitHub [@wds15](https://github.com/wds15))
for enabling multi-threaded calls from Julia to a single Stan model instance.

Thanks to Adrian Seyboldt (GitHub [@aseyboldt](https://github.com/aseyboldt))
for providing the Rust wrapper.
17 changes: 16 additions & 1 deletion docs/internals/ffi.rst
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,18 @@ as well as an extended
Note: One quirk of the ``.C`` interface is the requirement that all inputs and
return values are passed by pointers. This is the reason for the ``bridgestan_R`` files in the source.

Rust
____

The Rust interface uses two crates in addition to the built-in
`FFI types <https://doc.rust-lang.org/core/ffi/index.html>`__.
Some general guidance on Rust FFI can be found in the
`Rust Book <https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html#using-extern-functions-to-call-external-code>`__.

These are `bindgen <https://docs.rs/bindgen>`__, which generates the Rust bindings from the C headers,
and `libloading <https://docs.rs/libloading>`__, which provides easy dynamic library loading functionality.

Care is taken to provide "safe" Rust wrappers so that users of the crate do not need to use the `unsafe` keyword.

General Problems
----------------
Expand All @@ -75,7 +87,8 @@ must also be freed on that side. This means special consideration is
needed to pass strings back and forth between the languages,
and inspired some of the design decisions behind ideas like returning
the parameter names as a comma separated list, rather than the more "natural"
array of strings.
array of strings, and this is why error messages must be passed back to the
library in order to be freed.

Output Streams
______________
Expand All @@ -85,3 +98,5 @@ This is particularly relevant for error messaging, which is printed to the stand
error output ``stderr`` from C++. This does *not*, for example, correspond to the
``sys.stderr`` stream available from Python.

We tackle this problem through the use of an interface-provided callback function
when necessary.
3 changes: 2 additions & 1 deletion docs/languages.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Language Interfaces
===================

BridgeStan currently has clients in three languages, a public C API
BridgeStan currently has clients in four languages, a public C API
which underlies all the clients, and an example of a standalone program
written in C.

Expand All @@ -20,6 +20,7 @@ If you are missing these features in your favorite language, we would welcome a
languages/python
languages/julia
languages/r
languages/rust
languages/c-api


Expand Down
47 changes: 47 additions & 0 deletions docs/languages/rust.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
Rust Interface
==============

`See the BridgeStan Crate documentation on docs.rs <https://docs.rs/bridgestan>`__

----

Installation
------------

The BridgeStan Rust client is available on `crates.io <https://crates.io/crates/bridgestan>`__ and via ``cargo``:

.. code-block:: shell
cargo add bridgestan
To build and use BridgeStan models, a copy of the BridgeStan C++ source code
is required. Please follow the :doc:`Getting Started guide <../getting-started>`
or use the Rust client in tandem with an interface such as :doc:`Python <./python>`
which automates this process.

``STAN_THREADS=true`` needs to be specified when compiling a model, for more
details see the `API reference <https://docs.rs/bridgestan>`__.

Example Program
---------------

An example program is provided alongside the Rust crate in ``examples/example.rs``:

.. raw:: html

<details>
<summary><a>Show example.rs</a></summary>


.. literalinclude:: ../../rust/examples/example.rs
:language: Rust

.. raw:: html

</details>


API Reference
-------------

See docs.rs for the full API reference: `<https://docs.rs/bridgestan>`__
38 changes: 32 additions & 6 deletions rust/README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,43 @@
# BridgeStan from Rust

This is a Rust wrapper for [BridgeStan](https://roualdes.github.io/bridgestan/latest/).
[*View the BridgeStan documentation on Github Pages*](https://roualdes.github.io/bridgestan/latest/languages/rust.html).

It relies on [`bindgen`](https://docs.rs/bindgen/) and [`libloading`](https://docs.rs/libloading/).
This is a Rust wrapper for [BridgeStan](https://github.com/roualdes/bridgestan). It
allows users to evaluate the log likelihood and related functions for Stan models
natively from Rust.

The Rust wrapper does not have any functionality to compile Stan models.
Internally, it relies on [`bindgen`](https://docs.rs/bindgen/) and
[`libloading`](https://docs.rs/libloading/).

## Compiling the model

The Rust wrapper does not currently have any functionality to compile Stan models.
Compiled shared libraries need to be built manually using `make` or with the Julia
or Python bindings.

For safety reasons all Stan models need to be installed with `STAN_THREADS=true`.
When compiling a model using `make`, set the environment variable:

```bash
STAN_THREADS=true make some_model
```

When compiling a Stan model in python, this has to be specified in the `make_args`
argument:

```python
path = bridgestan.compile_model("stan_model.stan", make_args=["STAN_THREADS=true"])
```

If `STAN_THREADS` was not specified while building the model, the Rust wrapper
will throw an error when loading the model.

## Usage:

Run this example with `cargo run --example=example`.

```rust
use std::ffi::{OsStr, CString};
use std::ffi::CString;
use std::path::Path;
use bridgestan::{BridgeStanError, Model, open_library};

Expand All @@ -22,7 +48,7 @@ let path = Path::new(env!["CARGO_MANIFEST_DIR"])
.unwrap()
.join("test_models/simple/simple_model.so");

let lib = open_library(path).expect("Could not load compiled stan model.");
let lib = open_library(path).expect("Could not load compiled Stan model.");

// The dataset as json
let data = r#"{"N": 7}"#;
Expand All @@ -35,7 +61,7 @@ let seed = 42;
let model = match Model::new(&lib, Some(data), seed) {
Ok(model) => { model },
Err(BridgeStanError::ConstructFailed(msg)) => {
panic!("Model initialization failed. Error message from stan was {}", msg)
panic!("Model initialization failed. Error message from Stan was {}", msg)
},
_ => { panic!("Unexpected error") },
};
Expand Down
4 changes: 2 additions & 2 deletions rust/examples/make_model.rs → rust/examples/example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ fn main() {
.unwrap()
.join("test_models/simple/simple_model.so");

let lib = open_library(path).expect("Could not load compiled stan model.");
let lib = open_library(path).expect("Could not load compiled Stan model.");

// The dataset as json
let data = r#"{"N": 7}"#;
Expand All @@ -24,7 +24,7 @@ fn main() {
Ok(model) => model,
Err(BridgeStanError::ConstructFailed(msg)) => {
panic!(
"Model initialization failed. Error message from stan was {}",
"Model initialization failed. Error message from Stan was {}",
msg
)
}
Expand Down
Loading

0 comments on commit a184246

Please sign in to comment.