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


# Project::roster

Reads the accepted state of a [`Project`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/project/project.md>) without skills: scheduled leaf tasks, resources and active assignments, in model order. Use it to build your own [`Slot`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/suggest/slot.md>) list for [`suggest_assignments`](<https://docs.dhtmlx.com/planning-engine/rust/api/project-suggest-assignments.md>).

## Signature

```rust
pub fn roster(&self) -> Result<Roster, Error>
```

Takes `&self`. Synchronous and CPU-only.

## Returns

A [`Roster`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/suggest/roster.md>).

## Errors

| Error | When |
|---|---|
| [`Error::Project(ProjectError::NoBaseline)`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/errors/project-error.md>) | The project is unsolved or has unsolved edits |

## Example

Build one slot per scheduled task that has no assignment yet, open to every resource.

```rust
use plen_core::{
    AssignmentData, DateTime, Project, ProjectData, ResourceData, Slot, SolveOptions,
    SuggestOptions, TaskData,
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let monday = DateTime::from_ymd_hm(2026, 8, 10, 8, 0).ok_or("invalid date")?;
    let task = |id: &str| TaskData {
        start_date: Some(monday),
        duration: Some(2.0),
        ..TaskData::new(id)
    };
    let data = ProjectData {
        tasks: vec![task("a"), task("b")],
        resources: ["dev1", "dev2"]
            .iter()
            .map(|id| ResourceData { id: (*id).into(), capacity: None, calendar_id: None })
            .collect(),
        assignments: vec![AssignmentData {
            id: Some("x".into()),
            task: "a".into(),
            resource: "dev1".into(),
            value: 1.0,
            offset: None,
            duration: None,
        }],
        ..Default::default()
    };

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

    let roster = project.roster()?;
    let slots: Vec<Slot> = roster
        .tasks
        .iter()
        .filter(|task| !roster.assignments.iter().any(|a| &a.task == *task))
        .map(|task| Slot::new(format!("slot-{task}"), task.clone(), roster.resources.clone()))
        .collect();

    let result = project.suggest_assignments(&slots, &SuggestOptions::default())?;
    println!("{} slot(s), {} step(s)", slots.len(), result.steps.len());
    Ok(())
}
```

## Related

- [`Project`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/project/project.md#roster>) - the type and its other methods
- [`Roster`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/suggest/roster.md>) - the returned snapshot
- [`plen_assign::slots`](<https://docs.dhtmlx.com/planning-engine/rust/api/assign/slots.md>) - builds slots from skills instead
- [Suggesting assignments](<https://docs.dhtmlx.com/planning-engine/rust/resources-and-assignment/suggesting-assignments.md#slots>) - building slots by hand
