Repository: HypothesisWorks/hypothesis

Issue:
Tooling & conventions for Rust code
Hypothesis users show interest in `conjecture-rust` - #2632 and #2681 were opened recently, and #2680 resurrected Hypothesis-Ruby. As a continuation of these issues, I want to discuss how we should handle Rust code in this repository and agree on some practices we can apply for code, linting, CI, etc. 

Feel free to comment & share your thoughts :) 

### Rust version

I believe that we can target the `stable` Rust channel. Currently, I don't see any unstable feature that will dramatically improve the status quo, but we can always discuss & change the target channel.

### Minimum supported Rust version (MSRV)

As we distribute [conjecture](https://crates.io/crates/conjecture), it will be nice to agree on MSRV (currently it compiles successfully with Rust 1.34+). 

It is also a useful bit of information for people who will compile bindings, e.g., for downstream packages maintainers (but I am not aware of anybody who builds Hypothesis-Ruby).

As `conjecture-rust` is not 1.0 yet, we can probably stick with the latest stable Rust version, and once it is the case, we can set up additional tests to enforce MSRV.

### Linting

`clippy` is de-facto the standard linter, and I propose to use it :) The question is, what checks should we use - I tend to use the `pedantic` set of lints and explicitly disable them when they are false positive or not applicable (e.g., arithmetic-related lints)

### Code formatting

`rustfmt`? However, it might break some macros as in #2680, so we might need to tune it.

### CI

[actions-rs](https://github.com/actions-rs) provides a set of actions we can use; however, I don't know whether it is better to create a separate workflow file and put all jobs for `conjecture-rust` there or extend the existing one. Anyway, there are at least 4 jobs to include:

- `cargo fmt` - Code formatting
- `cargo clippy` - Lints
- `cargo test` - Tests
- https://github.com/actions-rs/audit-check - audits for crates with security vulnerabilities

### Releases

The current tooling seems to be working fine. At least the set of cargo commands looks exactly as it should be :)

---

As an outcome, I want to document these aspects, add CI jobs, and apply lint fixes & formatting.


Discussion:
Quick responses:

**Rust versions:** I agree we should stick to `stable`.  For our MSRV, a Rust toolchain is a build-time dependency that very few end-users should need.  On that basis (and the ease of updating Rust) I'm comfortable requiring fairly recent versions - e.g. an informal policy of "we will only test on latest stable for now, but will accept compat patches for (eg) toolchains up to six months old", and add that to CI once we're shipping Rust in Python.

**Linting and auto-formatting:** I also support the pedantic-or-annotated approach to linters, as well as automatically fixing as much as possible, and would be happy to accept your PR adding them and appropriate contributor docs.

**CI:** for consistency, let's do this via `build.sh` and our `hypothesistooling` helper package - it already handles installing Rust, for example!  Some of these jobs can also be folded into `check-whole-repo-tests`, which already e.g. checks the pinned Python dependencies.

And thanks again for taking this on 😍 
I would say that this is almost always a good practice to use `nightly` with `allow_failure` in the CI to test future regressions.

There one more thing to notice: current CI setup is pretty heavy. And it would be awesome to trigger different checks on different file changes. Like: changes in `hypothesis-python` should not trigger `rust` CI and so on.
Docs: https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#onpushpull_requestpaths

I would love to work on Rust version as well, I don't have enough of my Rust practice. @Stranger6667 feel free to assign some simple things to me 👍 
Thank you for your feedback! I will submit PRs for these areas step by step :)

Here is the first one - #2751 :)

@Zac-HD

> And thanks again for taking this on 

You're welcome :) It is an extremely interesting thing to do :)

@sobolevn

> I would love to work on Rust version as well, I don't have enough of my Rust practice. @Stranger6667 feel free to assign some simple things to me +1

Sure thing! 


> There one more thing to notice: current CI setup is pretty heavy. And it would be awesome to trigger different checks on different file changes. Like: changes in `hypothesis-python` should not trigger rust CI and so on.

We do already have this logic in our `hypothesistooling` helper package - if there are no changes to the Python code *or to the tooling*, then the tests won't actually run.  In principle I don't mind adding CI-job-level filtering to replicate this, but I would be a little concerned about getting out of sync and about potential interference with required checks - it's not like setting up a cached environment and deciding to skip the tests is a lot of work.

> I would say that this is almost always a good practice to use nightly with allow_failure in the CI to test future regressions.

And the same for Python `3.10-dev` would be lovely (currently failing because Numpy doesn't build, probably fixable).  Now that we're on GitHub Actions making our CI fancier does make sense again 😄 

Resolving patch:
diff --git a/tooling/src/hypothesistooling/__main__.py b/tooling/src/hypothesistooling/__main__.py
--- a/tooling/src/hypothesistooling/__main__.py
+++ b/tooling/src/hypothesistooling/__main__.py
@@ -475,6 +475,27 @@ def check_ruby_tests():
     hr.rake_task("minitest")
 
 
+@ruby_task
+def format_rust_in_ruby():
+    hr.cargo("fmt")
+
+
+@ruby_task
+def check_rust_in_ruby_format():
+    hr.cargo("fmt", "--", "--check")
+
+
+@ruby_task
+def lint_rust_in_ruby():
+    hr.cargo("clippy")
+
+
+@ruby_task
+def audit_rust_in_ruby():
+    hr.cargo("install", "cargo-audit")
+    hr.cargo("audit")
+
+
 @task()
 def python(*args):
     os.execv(sys.executable, (sys.executable,) + args)
@@ -508,6 +529,12 @@ def lint_conjecture_rust():
     cr.cargo("clippy")
 
 
+@rust_task
+def audit_conjecture_rust():
+    cr.cargo("install", "cargo-audit")
+    cr.cargo("audit")
+
+
 @task()
 def tasks():
     """Print a list of all task names supported by the build system."""
diff --git a/tooling/src/hypothesistooling/projects/hypothesisruby.py b/tooling/src/hypothesistooling/projects/hypothesisruby.py
--- a/tooling/src/hypothesistooling/projects/hypothesisruby.py
+++ b/tooling/src/hypothesistooling/projects/hypothesisruby.py
@@ -140,6 +140,12 @@ def ensure_bundler():
     bundle_command("install")
 
 
+def cargo(*args):
+    install.ensure_rustup()
+    with in_dir(BASE_DIR):
+        subprocess.check_call(("cargo",) + args)
+
+
 RUBY_TO_PRINT_VERSION = """
 require 'rubygems'
 spec = Gem::Specification::load(%r)


Test patch: