Install and feature surfaces
One package, four build surfaces. Which one you need, and what each pulls into your dependency graph.
candid-core is one crate on crates.io. It reads Candid
.did interface source and projects it into a Contract:
a validated, canonically ordered type graph with named declarations, an optional
actor, and content-addressed identities computed over its canonical bytes. What
that crate builds is split into an always-present base plus three Cargo
features, so a consumer that only wants the data model never compiles a Candid
parser and never links a filesystem capability crate.
Everything on this page is pre-1.0. The current release is
0.1.0-beta.3, and any release before 1.0 may change the public API,
the serialized shapes, the canonical bytes, and therefore every identity computed
over them. Pin an exact version.
Adding the dependency#
Every published version of candid-core is a prerelease. Cargo only
selects a prerelease when the version requirement itself names one, so the usual
caret spelling resolves to nothing: candid-core = "0.1" does
not match 0.1.0-beta.3, and cargo install candid-core
fails with a "could not find … with version *" message. Write the exact
requirement.
[dependencies]
# The full surface: compiler, filesystem-compiler and host-value are all
# enabled by default, so no feature selection is needed.
candid-core = "=0.1.0-beta.3"The same thing from the command line:
cargo add candid-core@=0.1.0-beta.3
Pinning with = is worth doing for a second reason beyond
prerelease selection. With both the API and the wire format unstable before 1.0,
an automatic move to the next prerelease is a change you should opt into
deliberately rather than inherit from a cargo update.
0.1.0-beta.1 and 0.1.0-beta.2 walk a declaration graph
as a tree, so a small .did file with shared type aliases can drive
work that doubles per level — and no limit refused it, because the walk returned
Ok. 0.1.0-beta.3 fixes that and adds
max_type_preflight_work so the work is charged. If you compile
.did source you did not write, do not pin either earlier version.
The four build surfaces#
default = ["compiler", "filesystem-compiler", "host-value"]. The base
is what remains when default-features = false removes all three.
| Surface | What it adds | What it pulls into your graph |
|---|---|---|
| (base) |
Contract, ContractDraft, RawContract,
ContractEnvelope, validation, canonicalization, the semantic
Contract identities, artifact_id_with_limits /
ArtifactKind, Limits / RuntimeContext /
CancellationToken, Diagnostic
|
serde, serde_json, sha2, hex |
| host-value |
HostValue, HostFieldValue,
validate_host_value, ContractTypeRef /
ContractMethodRef
|
ic_principal |
| compiler |
compile_did and its option/context variants,
compile_with_resolver, Compilation,
CompileOptions, CompileError,
SourceId / SourceResolver /
ResolvedSource / MemoryResolver, the
SourceInfo provenance sidecar
|
candid, candid_parser |
filesystem-compiler (implies compiler) |
WorkspaceResolver, compile_did_file and its
variants, source materialization for
candid_parser::check_file, the candid-core binary
|
cap-std |
Base — the pure Contract model#
The base surface can build a Contract from a graph you already hold
(ContractDraft), validate one that arrived as JSON
(Contract::from_json_with_context), canonicalize it, read its
contract_id and interface_id, and hash a serialized
document into a detached artifact identity. It cannot parse .did text,
because no Candid engine is in the graph at all.
Choose it if your process receives Contract documents rather than
Candid source — a registry that stores them, a service that validates uploads, a
build step downstream of someone else's compile. What that removes is a
dependency-graph size you can measure: resolved for
x86_64-unknown-linux-gnu, the base graph is 22 packages where the
default graph is 126. The figures move with the target, because
cap-std brings different platform dependencies on Linux, macOS and
Windows, so
verify_feature_graph.py
resolves and prints every configuration for the host you run it on.
host-value — validating values against a graph#
Adds HostValue, a lossless self-describing JSON encoding for Candid
values, and validate_host_value, which checks a value tree against a
type node in a Contract's graph. It takes ic_principal directly rather
than through candid, which is what keeps the parser stack out of a
value-only build.
Choose it if you accept user-entered or wire-received values and want the interface itself to decide whether they are well-formed. See Validating host values.
compiler — Candid source, no filesystem#
Adds the parsing and type-checking path. Parsing and semantic checking are
delegated to the official candid_parser crate rather than
reimplemented, so this feature is what brings candid and
candid_parser into the graph. It covers self-contained sources
through compile_did and multi-file bundles with imports
through compile_with_resolver, where the host supplies every source
synchronously as data.
Choose it if you compile Candid source in a place with no filesystem, or with a filesystem you do not want the library to touch: a browser, a sandboxed worker, an editor that already has the buffers in memory, a server accepting pasted source.
filesystem-compiler — native files#
Adds WorkspaceResolver, which holds an open cap-std
directory capability for one authorized root and opens every path relative to it;
compile_did_file, which roots a resolver at the file's own parent
directory; and the candid-core binary. It implies
compiler, so it is a superset.
Choose it if you are a native tool that reads .did
files off disk, or you want the CLI. See
The candid-core binary.
What feature selection does, and does not, do#
Two caveats are worth stating plainly, because both are easy to over-read.
Cargo unifies features across a build. Feature splitting bounds
what a dependency graph must contain — it cannot subtract from a graph
that already asked for more. If anything else in your build depends on
candid-core with defaults, the whole surface is compiled once for
every consumer in that build, including yours. Your
default-features = false line is still meaningful: it states what
your crate needs, and it is what makes a lean graph possible when nothing
else contradicts it. It is not a guarantee about the compiled output of a mixed
workspace.
Feature selection does not shrink the download. Every published
source file ships in the .crate archive regardless of features, so a
base-only consumer still downloads the compiler sources it will never
build. What the archive contains is governed separately, by a positive
include allowlist in the manifest — production source, runnable
examples, the public docs/ set, and the three root documents. Test
suites, fixtures, benchmarks, the fuzz crate and CI assets are not published.
One thing features do not change: ProducerInfo::current
reports the same name, version,
candid_version and candid_parser_version in every
configuration, because it reads the pinned versions out of the manifest at compile
time rather than from a linked crate.
Two configurations worth copying#
A browser or WASM host that compiles source it already holds#
[dependencies]
candid-core = { version = "=0.1.0-beta.3", default-features = false, features = ["compiler"] }
This keeps cap-std out of the graph entirely — and so does the default
configuration on that target, because cap-std is declared under
cfg(not(target_os = "unknown")) as well as behind the feature. A
compiler-only graph resolved for wasm32-unknown-unknown is
107 packages. Compile a bundle the host supplies:
use candid_core::{compile_with_resolver, CompileOptions, MemoryResolver, RuntimeContext};
let mut resolver = MemoryResolver::new();
resolver.insert(
"api/root.did",
r#"import "types.did"; service : { read: () -> (Item) query };"#,
)?;
resolver.insert(
"api/types.did",
"type Item = record { id: nat64; label: text };",
)?;
let compilation = compile_with_resolver(
"api/root.did",
&resolver,
CompileOptions::default(),
&RuntimeContext::default(),
)?;
println!("contract: {}", compilation.contract().contract_id());A pure-model consumer that never sees Candid source#
[dependencies]
candid-core = { version = "=0.1.0-beta.3", default-features = false }use candid_core::{Contract, ContractJsonError, Limits, RuntimeContext};
fn accept(document: &[u8]) -> Result<Contract, ContractJsonError> {
// The byte gate runs before serde_json is invoked, so an oversized
// document is rejected without being decoded; the gate, the decode and
// validation then share one budget.
let context = RuntimeContext::new(Limits::default().with_max_input_bytes(256 * 1024));
let contract = Contract::from_slice_with_context(document, &context)?;
println!("{} type nodes", contract.types().len());
println!("contract identity {}", contract.contract_id());
Ok(contract)
}
Add features = ["host-value"] to that line if the same process also
validates Candid values against the graph.
Items outside the enabled set are absent at compile time rather than present as
stubs that fail at run time. If you call compile_did from a
base-only build, the error names the missing item and the fix is to turn on the
feature it belongs to.
Toolchain, targets and licence#
- Minimum supported Rust version
-
1.78, declared as
rust-versionin the manifest. CI runs the locked dependency graph against 1.78.0, so a direct or transitive update that breaks it fails before merge. - Edition
- 2021, across every crate in the repository.
- Licence
- Apache-2.0.
- Native targets exercised in CI
- Linux, macOS and Windows on the rolling stable channel, plus Linux on 1.78.0 for MSRV.
- WebAssembly
-
wasm32-unknown-unknown. CI checks the library there in four configurations — base,host-value,compilerand defaults — and lints thecompilerone with warnings denied. The browser runtime suite then compiles source and an imported bundle inside headless Chrome and pins the resulting identities, so running there is a runtime claim rather than only a build claim.
The crate refuses to compile on targets whose pointer width is neither 32 nor 64
bits, with a message explaining why: portable wire values are fixed-width
u64 widened from usize, and the default limit values do
not fit a 16-bit usize.
Two things bare wasm32-unknown-unknown cannot do#
The compiler surface needs no filesystem and both builds and runs on
that target. Two limitations are stated rather than papered over.
-
There is no directory to open.
WorkspaceResolverandcompile_did_filestill compile for bare WASM, butWorkspaceResolver::newfails there withdid_workspace_root_error, and a load would fail withdid_file_read_error. UseMemoryResolveror your ownSourceResolverinstead. -
There is no clock. Cancellation and every quantitative limit
behave exactly as they do natively. An explicit
Limits::with_deadline_unix_mscannot be measured, so it fails closed withoperation_deadline_exceededrather than calling an unsupportedstd::timefunction and aborting the module;Limits::deadline_exceededreports the same. No deadline configured stays unbounded, as it is everywhere else. The crate takes noweb-time,js-sysorwasm-bindgenproduction dependency to change that.
Next#
The three entry points, their variants, and a runnable example of each.
Rust crate Building ContractsProducing a Contract with no parser, and validating one that arrived from elsewhere.
Start here Packages and cratesEvery publishable and internal unit in the repository, and how to depend on it.