---
title: Rust SDK
description: "Rust SDK: ask the ten ThinkThen questions from Rust and get yes, no or not sure back as plain Rust values."
goal: The reader runs a first decide in Rust, adds the crate, handles not sure and failures, and finds the Rust call for each of the ten functions.
reader: A Rust developer who wants to ask questions about text from code
kind: reference
verified: 2026-11-03
sidebar:
  label: "Rust"
---

# Rust SDK

The Rust SDK gives your Rust code the ten functions as methods on one engine; each call gives back a plain Rust value, such as `true` or `false` from decide, or an `Err` when the answer is not sure or ThinkThen can't judge the text.

```rust
use thinkthen::{Decide, Engine};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let engine = Engine::from_env()?;
    let refund = Decide::new("Does the customer request a refund?")?;
    let is_refund: bool = engine
        .run(&refund, "Please refund my order.")?
        .into_answer()?;
    println!("{is_refund}");
    Ok(())
}
```

Output

```text
true
```


`true` means yes: the message asks for a refund.

Add the crate (Rust 1.95.0 or later):

```sh
cargo add thinkthen@0.2.0 --no-default-features
```

The engine is inside the crate, so `cargo run` is all it needs. `Engine::from_env()` uses the key from the [Quickstart](/quickstart/).

## Handle not sure and failures

Set a low bar and a high bar with `Threshold::band`. Between them, decide is not sure, and `into_answer()` returns `Err(NoAnswer::Unsure)` with the probability. When ThinkThen can't judge the message, it returns `Err(NoAnswer::Failed)` with the error instead. Neither one can pass for `false`.

```rust
use thinkthen::{Decide, Engine, NoAnswer, Threshold};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let engine = Engine::from_env()?;
    let refund = Decide::new("Does the customer request a refund?")?
        .threshold(Threshold::band(0.2, 0.8)?);
    let message = "I was charged twice for one invoice.";
    match engine.run(&refund, message)?.into_answer() {
        Ok(is_refund) => println!("{is_refund}"),
        Err(NoAnswer::Unsure { probability }) => println!(
            "Not sure ({}): ask a person.",
            probability.get()
        ),
        Err(NoAnswer::Failed(error)) => {
            println!("{}: {error}", error.code())
        }
    }
    Ok(())
}
```

Output

```text
Not sure (0.7): ask a person.
```


A double charge might be a refund request, or might not, so the answer is not sure. Send it to a person.

`probability.get()` is the chance of yes. `error.code()` names the error, such as `record.missing`; [Troubleshooting](/reference/troubleshooting/) lists each one. With `?`, not sure and a failure both stop the function. `answer()` borrows the answer instead of taking it.

`run` itself returns `Err` only when the call can't go ahead, such as blank text (`record.empty`) or a cancelled call.

## The ten functions

Each function reads the same support-inbox messages as the function pages.

### decide

<details class="example-disclosure">
<summary>Yes or no about one message</summary>

```rust
use thinkthen::{Decide, Engine};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let engine = Engine::from_env()?;
    let refund = Decide::new("Does the customer request a refund?")?;
    // into_answer() fails on not sure or a failed record, so neither
    // reads as false.
    let is_refund: bool = engine
        .run(&refund, "Please refund my order.")?
        .into_answer()?;
    println!("{is_refund}");
    Ok(())
}
```

Output

```text
true
```


Yes: the message asks for a refund. `Decide` takes `.threshold(...)` (one bar with `Threshold::cut`, or a low and a high bar with `Threshold::band`), `.yes_means(...)` and `.no_means(...)`. [decide](/functions/decide/) shows every option.

</details>

### choose

<details class="example-disclosure">
<summary>The one choice that fits</summary>

```rust
use thinkthen::{Choose, Engine};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let engine = Engine::from_env()?;
    let about = Choose::new(
        "What is the message about?",
        ["billing", "shipping", "other"],
    )?;
    let topic: String = engine
        .run(&about, "Please refund my order.")?
        .into_answer()?;
    println!("{topic}");
    Ok(())
}
```

Output

```text
billing
```


A refund is about billing. `Choose` takes `.descriptions(...)`, each choice paired with what it means, and `.threshold(...)`, which can make choose not sure. [choose](/functions/choose/) shows every option.

</details>

### filter

<details class="example-disclosure">
<summary>Only the messages that answer yes</summary>

```rust
use thinkthen::{Engine, Filter};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let engine = Engine::from_env()?;
    let messages = vec![
        "Please refund my order.",
        "Where is my parcel?",
        "I was charged twice for one invoice.",
    ];
    let refund = Filter::new("Does the customer request a refund?")?;
    // Only the messages that answer yes.
    // A failed message arrives as an Err in its place.
    for refund_request in engine.filter(&refund, messages, |message| *message)?
    {
        println!("{}", refund_request?);
    }
    Ok(())
}
```

Output

```text
Please refund my order.
I was charged twice for one invoice.
```


The parcel question is left out. `engine.filter` gives back your own items, and leaves out the ones it is not sure about. The closure picks the text to judge from each item. A message that fails arrives as an `Err` in its place, holding the item, and reading goes on. `engine.filter_items` gives every item with its outcome instead. [filter](/functions/filter/) shows every option.

</details>

### tag

<details class="example-disclosure">
<summary>Every label that fits</summary>

```rust
use thinkthen::{Engine, Tag};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let engine = Engine::from_env()?;
    let appear = Tag::new("Which topics appear?", ["billing", "shipping"])?;
    let topics: Vec<String> = engine
        .run(&appear, "I was charged twice and my parcel is late again.")?
        .into_answer()?;
    println!("{topics:?}");
    Ok(())
}
```

Output

```text
["billing", "shipping"]
```


The message is about both billing and shipping. The answer is a `Vec<String>`, empty when no label fits. [tag](/functions/tag/) shows every option.

</details>

### score

<details class="example-disclosure">
<summary>A number from 0 to 1 on your scale</summary>

```rust
use thinkthen::{Engine, Score};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let engine = Engine::from_env()?;
    let how_urgent =
        Score::new("How urgent is the request?", ["routine", "urgent"])?;
    // 0 is routine, 1 is urgent.
    let urgency: f64 = engine
        .run(&how_urgent, "Please refund my order.")?
        .into_answer()?;
    println!("{urgency}");
    Ok(())
}
```

Output

```text
0.05
```


The levels run lowest first, so a plain refund request scores near routine. [score](/functions/score/) shows every option.

</details>

### rank

<details class="example-disclosure">
<summary>Every message, highest first</summary>

```rust
use thinkthen::{Engine, Rank};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let engine = Engine::from_env()?;
    let messages = vec![
        "Please refund my order.",
        "Where is my parcel?",
        "I was charged twice for one invoice.",
    ];
    let by_urgency =
        Rank::new("How urgent is the request?", ["routine", "urgent"])?;
    // Rank 1 is the most urgent.
    for ranked in engine.rows(&by_urgency, messages)? {
        let ranked = ranked?;
        let rank = ranked.answer()?;
        let (_, message) = ranked
            .record()
            .expect("a rank row keeps its record")
            .original();
        println!("{rank} {}", String::from_utf8_lossy(message));
    }
    Ok(())
}
```

Output

```text
1 I was charged twice for one invoice.
2 Where is my parcel?
3 Please refund my order.
```


The double charge is the most urgent. Each answer is a `NonZeroUsize` rank, starting at 1, and `record()` holds the message it belongs to. `Rank` takes `.top(...)` to keep only the first few. [rank](/functions/rank/) shows every option.

</details>

### find

<details class="example-disclosure">
<summary>The line that answers the question</summary>

```rust
use thinkthen::{Engine, Find};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let engine = Engine::from_env()?;
    let message = "Morgan works for Example Group.\nPlease refund my order.";
    let requested = Find::new("Which line states the requested action?")?;
    // None when no line answers the question.
    match engine.run(&requested, message)?.into_answer()? {
        Some(action) => println!("{}", action.text()),
        None => println!("no line answers"),
    }
    Ok(())
}
```

Output

```text
Please refund my order.
```


The second line states the action. The answer is `Some(Found)`, with `text()`, `start()` and `end()`, or `None` when no line answers. Offsets count UTF-8 bytes, as Rust strings do, so `&message[action.start()..action.end()]` is the same text. [find](/functions/find/) shows every option.

</details>

### recognize

<details class="example-disclosure">
<summary>Each name in the text and its kind</summary>

```rust
use thinkthen::{Engine, Recognize};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let engine = Engine::from_env()?;
    let people_and_groups = Recognize::new(["person", "organization"])?;
    let names = engine
        .run(&people_and_groups, "Morgan works for Example Group.")?
        .into_answer()?;
    for name in &names {
        println!("{}: {}", name.kind(), name.text());
    }
    Ok(())
}
```

Output

```text
person: Morgan
organization: Example Group
```


Each `Name` has `text()`, `kind()`, `start()`, `end()` and `probability()`. [recognize](/functions/recognize/) shows every option.

</details>

### relate

<details class="example-disclosure">
<summary>Names linked by the relations you allow</summary>

```rust
use thinkthen::{Engine, Relate, RelationRule};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let engine = Engine::from_env()?;
    let message = "Morgan works for Example Group.\nPlease refund my order.";
    let works_for = Relate::new([RelationRule::new(
        "works_for",
        "person",
        "organization",
    )?])?;
    let relations = engine.run(&works_for, message)?.into_answer()?;
    for relation in &relations {
        println!(
            "{} --{}--> {}",
            relation.source().text(),
            relation.relation(),
            relation.target().text()
        );
    }
    Ok(())
}
```

Output

```text
Morgan --works_for--> Example Group
```


`RelationRule::new` takes the relation, then the kind of name at each end. `.reads(...)` words the relation, and `.either(true)` makes it work in both directions. [relate](/functions/relate/) shows every option.

</details>

### annotate

<details class="example-disclosure">
<summary>Several questions for each message</summary>

```rust
use thinkthen::{AnnotatableQuestion, Annotate, Choose, Decide, Engine, Score};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let engine = Engine::from_env()?;
    let messages = vec![
        "Please refund my order.",
        "Where is my parcel?",
        "I was charged twice for one invoice.",
    ];
    let triage = Annotate::new([
        (
            "refund",
            AnnotatableQuestion::from(Decide::new(
                "Does the customer request a refund?",
            )?),
        ),
        (
            "topic",
            Choose::new(
                "What is the message about?",
                ["billing", "shipping", "other"],
            )?
            .into(),
        ),
        (
            "urgency",
            Score::new("How urgent is the request?", ["routine", "urgent"])?
                .into(),
        ),
    ])?;
    for annotated in engine.rows(&triage, messages)? {
        // Each answer prints bare, or as unsure or failed with its code.
        let answers = annotated?.into_answer()?;
        println!(
            "{} {} {}",
            answers["refund"], answers["topic"], answers["urgency"]
        );
    }
    Ok(())
}
```

Output

```text
true billing 0.05
false shipping 0.14
true billing 0.45
```


Each answer is read by name. It prints as its answer, or as `unsure` or `failed:` with the error code; `answers.answer::<Decide>("refund")` takes it as a `Result`, as decide does. [annotate](/functions/annotate/) shows every option.

</details>

## The full API

`engine.rows(&question, messages)` takes any owned iterator and gives one result per message, in order. With the `stream` feature, `rows.into_stream()` is a `Stream` for async code; the crate brings no async runtime. Each question takes `.field("/text".parse()?)` to judge one field of a JSON message. Dropping the results, or calling `cancel()`, stops the call. The [Rust API reference](https://docs.rs/thinkthen/0.2.0/thinkthen/) lists every type and method.
