pub fn initialize_pty(&mut self) { let pty_system = native_pty_system();
// Get terminal size
let term_size = match crossterm::terminal::size() {
Ok((cols, rows)) => (cols as u16, rows as u16),
Err(_) => (constants::DEFAULT_WIDTH, constants::DEFAULT_HEIGHT), // Default size
};
let pair = match pty_system.openpty(PtySize {
rows: term_size.1,
cols: term_size.0,
pixel_height: 0,
pixel_width: 0,
}) {
Ok(pair) => pair,
Err(e) => {
eprintln!("Failed to open PTY: {}", e);
return;
}
};
// get the pty master and slave
let master = pair.master;
let slave = pair.slave;
// prepare the shell command
let mut cmd = CommandBuilder::new(DEFAULT_SHELL);
cmd.env("TERM", DEFAULT_TERM_TYPE);
// spawn the shell in the pty
let child = match slave.spawn_command(cmd) {
Ok(child) => child,
Err(e) => {
eprintln!("failed to spawn command: {}", e);
return;
}
};
// store child process for cleanup
self.child_process = Some(child);
// Create a writer for sending input to the pty
let writer = master.take_writer();
self.pty_master_writer = Some(writer);
// Create a thread-safe reference to self for reader thread
let overlay_ref = Arc::new(Mutex::new(self));
let overlay_clone = Arc::clone(&overlay_ref);
// Thread to read from the PTY master and update the overlay content
thread::spawn(move || {
let mut reader = master.try_clone_reader().unwrap();
let mut buffer = [0u8, 1024];
loop {
match reader.read(&mut buffer) {
Ok(n) if n > 0 => {
let mut overlay = match overlay_clone.lock() {
Ok(guard) => guard,
Err(_) => break, //lock poisoned
};
overlay.update_terminal_content(&buffer[..n]);
}
Ok(_) => break, //EOF
Err(e) => {
eprintln!("Error reading from PTY: {}", e);
break;
}
}
}
});
}
fn update_terminal_content(&mut self, new_content: &[u8]) {
self.terminal_buffer.extend_from_slice(new_content);
}
borrowed data escapes outside of method │ self escapes the method body here –>> let overlay_ref = Arc::new(Mutex::new(self));
You’re getting this error because self
is a mutable reference to a non-'static
value, and you’re trying to move it into a thread::spawn
closure — which requires 'static
lifetime. That won’t work since Rust can’t guarantee self
will live long enough.
let overlay_ref = Arc::new(Mutex::new(self));
You’re wrapping &mut self
in an Arc<Mutex<_>>
and sending it to a background thread — but self
can’t be 'static
, so the compiler rejects it.
Move shared state that needs to be mutated by the background thread into its own Arc<Mutex<...>>
field of the struct instead of wrapping self
.
Example refactor:
// inside your struct
terminal_buffer: Arc>>,
Then in update_terminal_content
:
fn update_terminal_content(&self, new_content: &[u8]) {
if let Ok(mut buf) = self.terminal_buffer.lock() {
buf.extend_from_slice(new_content);
}
}
In initialize_pty
, pass Arc<Mutex<Vec<u8>>>
into the thread, not self
.
Instead of accessing self
in the thread, send the data back to the main thread via a channel.
let (tx, rx) = std::sync::mpsc::channel();
thread::spawn(move || {
let mut reader = master.try_clone_reader().unwrap();
let mut buffer = [0u8; 1024];
loop {
match reader.read(&mut buffer) {
Ok(n) if n > 0 => {
if tx.send(Vec::from(&buffer[..n])).is_err() {
break;
}
}
Ok(_) | Err(_) => break,
}
}
});
Then poll rx
in your UI/main loop and call self.update_terminal_content()
safely from there.
Which approach fits your architecture better — background-owned buffer or main-thread-driven updates?
terminal_buffer: Vec::new(), update this line in constructor if i go with arc mutex
If you switch terminal_buffer
to an Arc<Mutex<Vec<u8>>>
, update your constructor line like this:
terminal_buffer: Arc::new(Mutex::new(Vec::new())),
Make sure your struct field is also declared accordingly:
terminal_buffer: Arc>>,
Want the full constructor snippet adjusted too?
i went with your first approach and it still says borrowed data escapes outside of method
Good call going with the first approach — but if you’re still seeing “borrowed data escapes”, you likely still have this line (or equivalent) inside initialize_pty
:
let overlay_ref = Arc::new(Mutex::new(self));
Even if terminal_buffer
is safely wrapped, self itself is still being moved into a thread, which is the core problem.
Instead, clone only the buffer and move that into the thread:
pub struct Overlay {
terminal_buffer: Arc>>,
// other fields...
}
Overlay {
terminal_buffer: Arc::new(Mutex::new(Vec::new())),
// ...
}
let buffer_clone = Arc::clone(&self.terminal_buffer);
thread::spawn(move || {
let mut reader = master.try_clone_reader().unwrap();
let mut buffer = [0u8; 1024];
loop {
match reader.read(&mut buffer) {
Ok(n) if n > 0 => {
if let Ok(mut buf) = buffer_clone.lock() {
buf.extend_from_slice(&buffer[..n]);
}
}
Ok(_) => break, // EOF
Err(e) => {
eprintln!("Error reading from PTY: {}", e);
break;
}
}
}
});
self
into Arc<Mutex<>>
'static
(like your Arc<Mutex<_>>
)Want help splitting out shared state if more fields need to go into the thread?
pub fn initialize_pty(&mut self) { let pty_system = native_pty_system();
// Get terminal size
let term_size = match crossterm::terminal::size() {
Ok((cols, rows)) => (cols as u16, rows as u16),
Err(_) => (constants::DEFAULT_WIDTH, constants::DEFAULT_HEIGHT), // Default size
};
let pair = match pty_system.openpty(PtySize {
rows: term_size.1,
cols: term_size.0,
pixel_height: 0,
pixel_width: 0,
}) {
Ok(pair) => pair,
Err(e) => {
eprintln!("Failed to open PTY: {}", e);
return;
}
};
// get the pty master and slave
let master = pair.master;
let slave = pair.slave;
// prepare the shell command
let mut cmd = CommandBuilder::new(DEFAULT_SHELL);
cmd.env("TERM", DEFAULT_TERM_TYPE);
// spawn the shell in the pty
let child = match slave.spawn_command(cmd) {
Ok(child) => child,
Err(e) => {
eprintln!("failed to spawn command: {}", e);
return;
}
};
// store child process for cleanup
self.child_process = Some(child);
// Create a writer for sending input to the pty
let writer = master.take_writer();
self.pty_master_writer = Some(writer);
let buffer_clone = Arc::clone(&self.terminal_buffer);
// Thread to read from the PTY master and update the overlay content
thread::spawn(move || {
let mut reader = master.try_clone_reader().unwrap();
let mut buffer = [0u8, 1024]; //ERROR the literal 1024 does not fit into the type u8 whose range is 0..=255
loop {
match reader.read(&mut buffer) {
Ok(n) if n > 0 => {
if let Ok(mut buf) = buffer_clone.lock() {
buf.extend_from_slice(&buffer[..n]);
}
}
Ok(_) => break, //EOF
Err(e) => {
eprintln!("Error reading from PTY: {}", e);
break;
}
}
}
});
} # ChatGPT You're seeing this:
let mut buffer = [0u8, 1024];
which attempts to initialize an array with 2 elements: 0u8
and 1024
— the second one overflows u8
. You meant to declare a buffer of 1024 bytes.
Use array syntax with size:
let mut buffer = [0u8; 1024];
This creates an array of 1024 elements, each initialized to 0u8
.
Let me know if you want to wire this into tview
redraw logic or stream it to a UI buffer.