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


# LinkAdd

A new dependency between two tasks, added to a live project through [`SchedulingChanges::links`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/changes/scheduling-changes.md>)`.add`. It has the same fields as [`LinkData`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/data/link-data.md>), except that `id` is optional. Build one with a struct literal. `LinkAdd` has no constructor and no `Default`, so every field is set.

## Declaration

```rust
#[derive(Clone, Debug)]
pub struct LinkAdd {
    pub id: Option<Id>,
    pub source: Id,
    pub target: Id,
    pub link_type: LinkType,
    pub lag: f64,
    pub lag_unit: Option<TimeUnit>,
}
```

## Fields

| Field | Type | Default | Description |
|---|---|---|---|
| `id` | `Option<`[`Id`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/data/id.md>)`>` | `None` | Id of the new link. `None` lets the engine generate one. |
| `source` | [`Id`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/data/id.md>) | required | Id of the predecessor task. |
| `target` | [`Id`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/data/id.md>) | required | Id of the successor task. |
| `link_type` | [`LinkType`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/data/link-type.md>) | required | Which end of `source` drives which end of `target`. |
| `lag` | `f64` | required | Working time between the two ends, in `lag_unit`. Must be finite. |
| `lag_unit` | `Option<`[`TimeUnit`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/data/time-unit.md>)`>` | `None` | Unit of `lag`. `None` uses [`config.duration_unit`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/data/scheduling-config.md#duration_unit>). |

`source`, `target`, `link_type`, `lag` and `lag_unit` mean the same as on [`LinkData`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/data/link-data.md>), including what happens when an endpoint names no task.

### id

```rust
pub id: Option<Id>
```

`Some(id)` stores the link under that id. It must not repeat the id of a link already in the project.

`None` makes the engine pick the first free id of the form `$generated_link_1`, `$generated_link_2`, and so on. The generated id is listed in [`ChangeEffects::generated_links`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/changes/change-effects.md>) of the [`ApplyReport`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/changes/apply-report.md>), so you can store it for later updates or removals.

## Errors

A `LinkAdd` is one operation, named `links.add[i]`. It's rejected, and listed as a [`RejectedOperation`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/changes/rejected-operation.md>) in [`ApplyReport::rejected`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/changes/apply-report.md#rejected>) instead of applied, when:

- `id` is `Some` and a link with that id already exists: `duplicate link id`
- `lag` is `NaN` or infinite: `lag must be finite`

An endpoint that names no task, or names an unscheduled task, doesn't reject the operation; see [`ChangeEffects::dropped_links`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/changes/change-effects.md#dropped_links>).

A `LinkAdd` can reuse the id of a link removed in the same change set; see [operation order](<https://docs.dhtmlx.com/planning-engine/rust/api/core/changes/scheduling-changes.md#operation-order>).

## Example

Link task 1 to task 2 in a solved project, and read back the generated link id:

```rust
use plen_core::{
    Changes, DateTime, LinkAdd, LinkType, Project, ProjectData, SchedulingChanges,
    SolveOptions, TaskData,
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let start = DateTime::from_ymd_hm(2026, 4, 6, 8, 0).ok_or("invalid date")?;
    let data = ProjectData {
        tasks: vec![
            TaskData { start_date: Some(start), duration: Some(2.0), ..TaskData::new(1) },
            TaskData { start_date: Some(start), duration: Some(3.0), ..TaskData::new(2) },
        ],
        ..Default::default()
    };

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

    let changes = SchedulingChanges {
        links: Changes {
            add: vec![LinkAdd {
                id: None,
                source: 1.into(),
                target: 2.into(),
                link_type: LinkType::Fs,
                lag: 0.0,
                lag_unit: None,
            }],
            ..Default::default()
        },
        ..Default::default()
    };

    let report = project.apply(&changes)?;
    println!("new link ids: {:?}", report.effects.generated_links);

    let result = project.solve(None, &options)?;
    println!("{:?}", result.updates);
    Ok(())
}
```

For the full edit cycle, see [Editing a project](<https://docs.dhtmlx.com/planning-engine/rust/live-projects/editing-a-project.md>).

## Related

- [`LinkData`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/data/link-data.md>) - the full link record with the same fields
- [`LinkUpdate`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/changes/link-update.md>) - edits a link that already exists
- [`ChangeEffects`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/changes/change-effects.md>) - reports generated and dropped link ids
- [Links, constraints and summaries](<https://docs.dhtmlx.com/planning-engine/rust/scheduling/links-constraints-summaries.md>) - explains how links drive dates
