This guide helps you migrate from other Expect-like libraries to rust-expect.
pexpect is the popular Python implementation of Expect. Here's how to translate common patterns.
pexpect (Python):
import pexpect
child = pexpect.spawn('/usr/bin/ftp speedtest.example.com')
# or with separate args
child = pexpect.spawn('/usr/bin/ssh', ['user@example.com'])rust-expect (Rust):
use rust_expect::prelude::*;
let mut session = Session::spawn("ftp speedtest.example.com")?;
// or with arguments
let mut session = Session::spawn_args("ssh", &["user@example.com"])?;pexpect (Python):
child.expect('Name \(.*\):') # Regex pattern
child.expect_exact('Password:') # Literal string
child.expect([pexpect.EOF, pexpect.TIMEOUT, 'prompt']) # Multiple patternsrust-expect (Rust):
use std::time::Duration;
session.expect_regex(r"Name \(.*\):")?; // Regex pattern
session.expect("Password:")?; // Literal string
// Multiple patterns
let result = session.expect_any(&[
Pattern::eof(),
Pattern::timeout(Duration::from_secs(10)),
Pattern::literal("prompt"),
]).await?;
match result.index() {
0 => println!("EOF received"),
1 => println!("Timeout"),
2 => println!("Got prompt"),
_ => unreachable!(),
}pexpect (Python):
child.send('hello') # Send without newline
child.sendline('hello') # Send with newline
child.sendcontrol('c') # Send Ctrl+C
child.sendeof() # Send EOF (Ctrl+D)rust-expect (Rust):
session.send("hello").await?; // Send without newline
session.send_line("hello").await?; // Send with newline
session.send_control('c').await?; // Send Ctrl+C
session.send_eof().await?; // Send EOF (Ctrl+D)pexpect (Python):
child = pexpect.spawn('cmd', timeout=30) # Default timeout
child.expect('pattern', timeout=10) # Per-operation timeout
try:
child.expect('pattern')
except pexpect.TIMEOUT:
print("Timeout!")rust-expect (Rust):
use std::time::Duration;
// Default timeout in config
let config = SessionConfig::default()
.with_timeout(Duration::from_secs(30));
let mut session = Session::spawn_with_config("cmd", config)?;
// Per-operation timeout
session.expect_timeout("pattern", Duration::from_secs(10)).await?;
// Handle timeout error
match session.expect("pattern").await {
Ok(result) => println!("Matched: {}", result.matched()),
Err(e) if e.is_timeout() => println!("Timeout!"),
Err(e) => return Err(e.into()),
}pexpect (Python):
child.interact() # Pass control to userrust-expect (Rust):
use rust_expect::interact::InteractOptions;
// Basic interactive mode
session.interact().await?;
// With hooks for pattern matching
let options = InteractOptions::new()
.on_output("password:", |ctx| {
eprintln!("Password prompt detected!");
Ok(())
});
session.interact_with(options).await?;pexpect (Python):
child.before # Text before the match
child.after # Text that matched
child.match # The match objectrust-expect (Rust):
let result = session.expect("pattern").await?;
result.before() // Text before the match
result.matched() // Text that matched
result.after() // Text after the match (in buffer)pexpect (Python):
if child.eof():
print("Process ended")
child.expect(pexpect.EOF) # Wait for EOFrust-expect (Rust):
if session.is_eof() {
println!("Process ended");
}
session.expect_eof().await?; // Wait for EOFexpectrl is another Rust Expect library. Here's how to migrate.
expectrl:
use expectrl::spawn;
let mut session = spawn("bash")?;
// SSH
let mut session = expectrl::spawn("ssh user@host")?;rust-expect:
use rust_expect::prelude::*;
let mut session = Session::spawn("bash")?;
// SSH with dedicated backend
let mut session = SshSessionBuilder::new("host")
.username("user")
.connect()?;expectrl:
use expectrl::{Regex, Eof};
session.expect(Regex("\\$ $"))?;
session.expect("literal string")?;
session.expect(Eof)?;rust-expect:
use rust_expect::expect::Pattern;
session.expect_regex(r"\$ $").await?;
session.expect("literal string").await?;
session.expect_eof().await?;expectrl:
session.send_line("echo hello")?;
session.send("raw bytes")?;rust-expect:
session.send_line("echo hello").await?;
session.send("raw bytes").await?;expectrl:
// Must enable async feature, uses different API
#[cfg(feature = "async")]
use expectrl::AsyncSession;
let mut session = AsyncSession::spawn("bash").await?;
session.expect("$ ").await?;rust-expect:
// Async is the default, sync is opt-in
use rust_expect::prelude::*;
let mut session = Session::spawn("bash")?;
session.expect("$ ").await?;
// For sync contexts
use rust_expect::sync::SyncSession;
let mut session = SyncSession::spawn("bash")?;
session.expect("$ ")?; // Blockingexpectrl:
use expectrl::interact::InteractSession;
let mut interact = InteractSession::new(&mut session, stream::stdin());
interact.spawn()?;rust-expect:
session.interact().await?;
// Or with more control
use rust_expect::interact::InteractOptions;
let options = InteractOptions::new()
.on_input("exit", |ctx| {
println!("User typed exit");
Ok(())
});
session.interact_with(options).await?;expectrl:
let captures = session.expect(Regex("user: (\\w+)"))?;
let matched = captures.get(1).unwrap();rust-expect:
let result = session.expect_regex(r"user: (\w+)").await?;
let matched = result.matched();
// For captures, use the pattern directly
if let Some(caps) = result.captures() {
let user = caps.get(1).map(|m| m.as_str());
}expectrl:
// Limited screen supportrust-expect:
use rust_expect::screen::ScreenBuffer;
let mut screen = ScreenBuffer::new(80, 24);
screen.feed(&output);
// Query screen content
let text = screen.get_text(0, 0, 80, 1); // First line
let found = screen.find_text("pattern");
// Visual diff
let diff = screen.diff(&other_screen);| Feature | pexpect | expectrl | rust-expect |
|---|---|---|---|
| Language | Python | Rust | Rust |
| Async default | No | No | Yes |
| Sync API | Yes | Yes | Optional |
| Windows support | Limited | Yes | Full ConPTY |
| SSH backend | External | Basic | Full (pooling, resilience) |
| Screen emulation | No | Basic | VT100 + visual diff |
| PII redaction | No | No | Built-in |
| Metrics | No | No | Prometheus/OTLP |
| Connection pooling | N/A | No | Yes |
| Mock testing | No | No | Built-in |
pexpect/expectrl pattern:
// expectrl uses Result with custom error types
match session.expect("pattern") {
Ok(m) => { /* handle match */ }
Err(e) => { /* handle error */ }
}rust-expect pattern:
use rust_expect::error::ExpectError;
match session.expect("pattern").await {
Ok(result) => {
println!("Matched: {}", result.matched());
}
Err(ExpectError::Timeout { duration, pattern, buffer }) => {
// Rich error context with buffer snippet
eprintln!("Timeout after {:?} waiting for '{}'", duration, pattern);
eprintln!("Buffer: {}", buffer);
}
Err(ExpectError::Eof { buffer }) => {
eprintln!("Process ended unexpectedly");
}
Err(e) => return Err(e.into()),
}pexpect:
child.expect('login:')
child.sendline('admin')
child.expect('password:')
child.sendline('secret')
child.expect('$')rust-expect with Dialog:
use rust_expect::dialog::{Dialog, DialogStep};
let dialog = Dialog::new()
.step(DialogStep::expect("login:").then_send("admin\n"))
.step(DialogStep::expect("password:").then_send("secret\n"))
.step(DialogStep::expect("$"));
session.run_dialog(&dialog).await?;pexpect:
# Manual management of multiple sessions
sessions = [pexpect.spawn(f'ssh host{i}') for i in range(3)]
for s in sessions:
s.expect('$')rust-expect:
use rust_expect::multi::{MultiSession, expect_all, expect_any};
let mut multi = MultiSession::new();
for i in 0..3 {
let session = Session::spawn(&format!("ssh host{}", i))?;
multi.add(session);
}
// Wait for all to match
let results = expect_all(&mut multi.sessions_mut(), "$").await?;
// Or wait for first match
let first = expect_any(&mut multi.sessions_mut(), "$").await?;The high-level Session and SyncSession APIs are unchanged, so most code needs
no changes. The breaking changes are confined to the low-level, re-exported PTY
handle:
PtyHandlenow wrapsrust-pty's master/child instead of a raw file descriptor. Code that reached into the raw fd should go through therust-ptytypes — or, preferably, useSession/SyncSession.PtyHandle::wait()was removed. Wait for the child throughSession/SyncSessioninstead.PtyHandle::signal()andPtyHandle::kill()were removed. Signal a child throughSession::signal/kill(or theSyncSessionequivalents), which guard against PID reuse.AsyncPty::signal()/kill()now take&mut self(they perform an authoritative reap check). Update any direct callers to hold a mutable binding;Session::signal/killkeep their&selfsignatures.