Let's create our project. For this example, we'll create a simple `len` command which will return the length of strings it's passed.
First off, we'll create our plugin:
```sh
cargo new nu_plugin_len
cd nu_plugin_len
```
Next, we'll add `nu` to our project's dependencies.
```sh
cargo add nu-plugin nu-protocol
```
The `Cargo.toml` file should now look something like the following.
```toml
[package]
name = "nu_plugin_len"
version = "0.1.0"
edition = "2024"
[dependencies]
nu-plugin = "0.104.0"
nu-protocol = "0.104.0"
```
With this, we can open up `src/main.rs` and create our plugin.
```rust
use nu_plugin::{EvaluatedCall, JsonSerializer, serve_plugin};
use nu_plugin::{EngineInterface, Plugin, PluginCommand, SimplePluginCommand};
use nu_protocol::{LabeledError, Signature, Type, Value};
struct LenPlugin;
impl Plugin for LenPlugin {
fn version(&self) -> String {
env!("CARGO_PKG_VERSION").into()
}
fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> {
vec![
Box::new(Len),
]
}
}
struct Len;
impl SimplePluginCommand for Len {
type Plugin = LenPlugin;
fn name(&self) -> &str {
"len"
}
fn description(&self) -> &str {
"calculates the length of its input"
}
fn signature(&self) -> Signature {
Signature::build(PluginCommand::name(self))
.input_output_type(Type::String, Type::Int)
}
fn run(
&self,
_plugin: &LenPlugin,
_engine: &EngineInterface,
call: &EvaluatedCall,
input: &Value,
) -> Result<Value, LabeledError> {
let span = input.span();
match input {
Value::String { val, .. } => Ok(
Value::int(val.len() as i64, span)
),
_ => Err(
LabeledError::new("Expected String input from pipeline")
.with_label(
format!("requires string input; got {}", input.get_type()),
call.head,
)
),
}
}
}
fn main() {
serve_plugin(&LenPlugin, JsonSerializer)
}
```
There are a few moving parts here, so let's break them down one by one.
First off, let's look at main:
```rust
fn main() {
serve_plugin(&LenPlugin, JsonSerializer)
}
```
In `main()`, we just call a single function `serve_plugin`. This will do the work of calling into our plugin, handling the JSON serialization/deserialization, and sending values and errors back to Nu for us. To start it up, we pass it something that implements the `Plugin` trait and something that implements the `PluginEncoder` trait. We're given a choice of serialization formats that Nu supports. Ordinarily plugins written in Rust should use `MsgPackSerializer` as it is considerably faster, but here we select JSON to demonstrate how the communication protocol works further on in this tutorial.
Above `main()` is the implementation of the `SimplePluginCommand` trait for the `len` command that our plugin will expose, which is represented by the `Len` type. We use `SimplePluginCommand` rather than `PluginCommand` in order to simplify our implementation and avoid [handling streams](#using-streams-in-plugins). Let's take a look at how we implement this trait:
```rust
impl SimplePluginCommand for Len {
type Plugin = LenPlugin;
// ...
}
```
We first specify the plugin type our command expects. This allows us to receive a reference to it in `run()`, which we can use for shared state between commands.
```rust
impl SimplePluginCommand for Len {
// ...
fn name(&self) -> &str {
"len"
}
fn description(&self) -> &str {
"calculates the length of its input"
}
fn signature(&self) -> Signature {
Signature::build(PluginCommand::name(self))
.input_output_type(Type::String, Type::Int)
}
// ...
}
```
There are a few methods required for this implementation. We first define the `name` of the command, which is what the user will type at the prompt or in their script to run the command. The `description` is also required, which is a short documentation string for users to know what the command does, and is displayed along with completions and in `help`. Finally, we define the `signature`, which specifies arguments and types for the command.