Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rust: documentation and release pipelines #130

Merged
merged 21 commits into from
Jun 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
WardBrian marked this conversation as resolved.
Show resolved Hide resolved
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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need an unsafe function to register a printing function, but I think that's probably not worth mentioning here, given that that's a pretty specialized use-case?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think that's the exception rather than the rule


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