[Canonical page](<https://docs.dhtmlx.com/planning-engine/rust/api/assign/slots/>)
[Full documentation index](<https://docs.dhtmlx.com/planning-engine/llms.txt>)


# slots

Turns a [`Pool`](<https://docs.dhtmlx.com/planning-engine/rust/api/assign/pool.md>) of workers and a list of [`Requirement`](<https://docs.dhtmlx.com/planning-engine/rust/api/assign/requirement.md>)s into the [`Slot`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/suggest/slot.md>)s that [`Project::suggest_assignments`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/project/project.md#suggest_assignments>) takes. Existing active assignments are bound to the slots, so repeated runs stay stable after a commit. Part of the `plen-assign` crate.

```bash
cargo add plen-assign
```

## Signature

```rust
pub fn slots(
    project: &Project,
    pool: &Pool,
    requirements: &[Requirement],
) -> Result<Vec<Slot>, MatchError>
```

Synchronous. Reads the project's accepted state through [`Project::roster`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/project/project.md#roster>) and never modifies the project.

## Parameters

| Name | Type | Description |
|---|---|---|
| `project` | [`&Project`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/project/project.md>) | A solved project with no unsolved edits. Tasks, resources and assignments are checked against its accepted state. |
| `pool` | [`&Pool`](<https://docs.dhtmlx.com/planning-engine/rust/api/assign/pool.md>) | Workers that may be assigned. Pool order sets the order of each slot's `allowed`. |
| `requirements` | [`&[Requirement]`](<https://docs.dhtmlx.com/planning-engine/rust/api/assign/requirement.md>) | What each task needs. An empty slice returns an empty `Vec`. |

## Returns

`Ok(Vec<Slot>)`: for each requirement, `count` slots, in requirement order, then instance order. Each slot is filled in as follows:

| Field | Value |
|---|---|
| `id` | `"<requirement id>#<n>"`, `n` = 1..=`count` (for example `be#1`, `be#2`) |
| `task` | The requirement's `task` |
| `allowed` | Pool workers for which every [`SkillMatch`](<https://docs.dhtmlx.com/planning-engine/rust/api/assign/skill-match.md>) of the requirement holds, in pool order. No matches allows every worker. |
| `value` | The requirement's `units` |
| `assignment` | The existing active assignment bound to this slot, or `None` |

On the next call, an assignment created from a slot binds back to that slot by id; see [`Requirement`](<https://docs.dhtmlx.com/planning-engine/rust/api/assign/requirement.md#id-stays-on-its-task>) for why a requirement id must stay on its task.

The result can be passed to [`suggest_assignments`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/project/project.md#suggest_assignments>) as is.

## Binding

The task's existing active assignments are bound per task, across all requirements on that task, in three steps:

1. **Slot id.** An assignment whose id is one of the requirement's slot ids binds to that slot, even when its resource isn't in `allowed`. The slot is then broken ([`AssignTarget::Broken`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/suggest/assign-target.md>)). This step runs for every requirement of the task before any explicit list.
2. **Explicit list.** [`Requirement::assignments`](<https://docs.dhtmlx.com/planning-engine/rust/api/assign/requirement.md#assignments>): `Some(list)` replaces automatic binding for that requirement. A listed slot-id assignment keeps its slot, the other entries take the remaining slots in order, and leftover slots stay empty. `Some(vec![])` leaves every slot empty.
3. **Matching.** A deterministic maximum bipartite matching binds the remaining assignments to the empty slots of requirements without a list. An assignment can take a slot only when its resource is in the slot's `allowed`. Input order decides between equal matchings, and an earlier requirement can't take the only worker a later one fits.

Assignments left unbound stay on the task without a slot.

## Errors

Returns [`MatchError`](<https://docs.dhtmlx.com/planning-engine/rust/api/assign/match-error.md>). The first failing check wins: the project state, then the pool, then each requirement in input order.

| Error | When it occurs |
|---|---|
| `MatchError::Project(`[`ProjectError::NoBaseline`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/errors/project-error.md>)`)` | The project is unsolved or has unsolved edits (after [`apply`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/project/project.md#apply>)). |
| `MatchError::Invalid` | The pool or a requirement is invalid. `path` names the item. |

`MatchError::Invalid` paths:

| Path | When it occurs |
|---|---|
| `pool.workers[i].resource` | Unknown resource, or the same resource listed as two workers |
| `pool.workers[i].skills[j].id` | The same skill listed twice on one worker |
| `pool.workers[i].skills[j].value` | Not finite |
| `requirements[i].id` | Duplicate requirement id, or one of its slot ids is an active assignment on another task |
| `requirements[i].task` | Unknown or unscheduled task |
| `requirements[i].count` | `0` |
| `requirements[i].units` | Not finite or `<= 0` |
| `requirements[i].matches[j].value` | Not finite |
| `requirements[i].assignments` | More entries than `count`, or an assignment with one of this requirement's slot ids left out of the list |
| `requirements[i].assignments[k]` | Unknown or inactive assignment, an assignment on another task, or one bound twice (the message names the other requirement when it already holds it) |

There's no `StaleBaseline`: the function takes no baseline token.

## Example

```rust
use plen_assign::{slots, Pool, Requirement, Skill, SkillMatch, Worker};
use plen_core::{
    DateTime, Project, ProjectData, ResourceData, SolveOptions, SuggestOptions, TaskData,
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut task = TaskData::new("B");
    task.start_date = DateTime::from_ymd_hm(2026, 8, 10, 8, 0);
    task.duration = Some(2.0);

    let resource = |id: &str| ResourceData { id: id.into(), capacity: None, calendar_id: None };
    let data = ProjectData {
        tasks: vec![task],
        resources: vec![resource("ann"), resource("bob")],
        ..Default::default()
    };

    let mut project = Project::default();
    project.solve(Some(data), &SolveOptions::default())?;

    let pool = Pool {
        workers: vec![
            Worker { resource: "ann".into(), skills: vec![Skill { id: "backend".into(), value: 1.0 }] },
            Worker { resource: "bob".into(), skills: vec![Skill { id: "backend".into(), value: 2.0 }] },
        ],
    };
    let requirements = [Requirement::new("be", "B", vec![SkillMatch::at_least("backend", 2.0)])];

    let slots = slots(&project, &pool, &requirements)?;
    // One slot "be#1" on task "B", allowed = ["bob"]

    let proposal = project.suggest_assignments(&slots, &SuggestOptions::default())?;
    project.apply(&proposal.changes)?;
    Ok(())
}
```

For the full workflow, see [Skills and requirements](<https://docs.dhtmlx.com/planning-engine/rust/resources-and-assignment/skills-and-requirements.md>).

## Related

- [`Project::suggest_assignments`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/project/project.md#suggest_assignments>) - consumes the returned slots
- [`Slot`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/suggest/slot.md>) - the type of each returned item
- [`SuggestOptions`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/suggest/suggest-options.md>) - options for the call the slots are passed to
- [Suggesting assignments](<https://docs.dhtmlx.com/planning-engine/rust/resources-and-assignment/suggesting-assignments.md>) - shows how the slots are filled and committed
- [Error handling](<https://docs.dhtmlx.com/planning-engine/rust/error-handling.md>) - shows how to handle the `MatchError` this function returns
