Проблема с временами жизни

Имеется массив u8 (принятый по TCP), нужно получить строковый срез этого массива. Массив и &str желательно держать в одной структуре.
То есть должно получиться что-то типа СИшного union’a - возможность чтения существующего массива как строкового среза без выделения дополнительных векторов, String и т.д.

Проблемы с временами жизни.
Можно ли как-нибудь это сделать не используя глобальную переменную static?
Как понять, какое время жизни имеет массив input_buf в структуре (видимо не 'static)?

use std::str;
use std::io::Write;
use std::io::Read;
use std::net::TcpListener;

pub struct Packet <'a> //Тут 'a, чтобы сделать src_packet не 'static
{
    pub input_buf: [u8; 512], //Какое время жизни у этого массива?
    pub src_packet: Option<&'a str>,
}


impl Packet <'static> //Тут просит указать время жизни. Какое указывать?
{
    ///Конструктор
    fn new() -> Packet <'static> //Тут просит указать время жизни. Какое указывать?
    {
        Packet
        {
            input_buf: [0u8; 512],
            src_packet: None,
        }
    }

    fn extract_pkt(&self)
    {
        self.src_packet = Some( str::from_utf8(&self.input_buf).unwrap() ); //Создание среза &str
    }
}


fn main()
{
    let mut pkt = Packet::new();

    let addr = "127.0.0.1:9999";
    let listener = TcpListener::bind(addr).unwrap();
    println!("Server listening at {}", addr);


    for stream in listener.incoming() //stream типа TcpStream
    {
        let mut stream = stream.unwrap();
        stream.read(&mut pkt.input_buf);

        pkt.extract_pkt(); //Вызов метода
    }
}

Build:

error[E0495]: cannot infer an appropriate lifetime for borrow expression due to conflicting requirements
  --> src\main.rs:29:48
   |
29 |         self.src_packet = Some( str::from_utf8(&self.input_buf).unwrap() );
   |                                                ^^^^^^^^^^^^^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the body at 28:4...
  --> src\main.rs:28:5
   |
28 | /     {
29 | |         self.src_packet = Some( str::from_utf8(&self.input_buf).unwrap() );
30 | |     }
   | |_____^
note: ...so that reference does not outlive borrowed content
  --> src\main.rs:29:48
   |
29 |         self.src_packet = Some( str::from_utf8(&self.input_buf).unwrap() );
   |                                                ^^^^^^^^^^^^^^^
   = note: but, the lifetime must be valid for the static lifetime...
note: ...so that expression is assignable (expected std::option::Option<&'static str>, found std::option::Option<&str>)
  --> src\main.rs:29:27
   |
29 |         self.src_packet = Some( str::from_utf8(&self.input_buf).unwrap() );
   |                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: aborting due to previous error

То есть, насколько я понял что-то не так со временем жизни input_buf.
Не соответствуют времена жизни массива input_buf и среза src_packet, При этом, я не понимаю какое время у input_buf.
Или проблема со временем жизни всей структуры?

Помогите пожалуйста разобраться, уже кучу вариантов перепробовал.

Что я вижу, так это самоссылающуюся структуру. Почитай цикл статей про это, чтобы понять, что такое самоссылающиеся структуры, почему возникла проблема, и как это можно решить:
Async/Await I: Self-Referential Structs
Async/Await II: Narrowing the Scope of the Problem
Async/Await III: Moving Forward with Something Shippable
Async/Await IV: An Even Better Proposal

Что в итоге вылилось в то, что недавно в стабильном Расте появился модуль std::pin, с помощью которого можно решить твой вопрос.

Но я бы предпочел изменить архитектуру и подход, обойтись без pin’ов, без диких времен жизни.

1 лайк

Думаю тебе не нужно хранить &str в структуре
str::from_utf8 не выделяет память под строку, а просто возвращает ссылку на эти же байты в виде строки, если это валидный utf8.

use std::io::Read;
use std::io::Write;
use std::net::TcpListener;
use std::str;

pub struct Packet<'a> {
    pub input_buf: &'a mut [u8],
}

impl<'a> Packet<'a> {
    fn new(input_buf: &mut [u8]) -> Packet<'_> {
        Packet { input_buf }
    }

    fn extract_pkt(&self) -> Option<&str> {
        str::from_utf8(self.input_buf).ok()
    }
}

fn main() {
    let mut buf = vec![0; 512];
    let pkt = Packet::new(&mut buf);

    let addr = "127.0.0.1:9999";
    let listener = TcpListener::bind(addr).unwrap();
    println!("Server listening at {}", addr);

    for stream in listener.incoming()
    {
        let mut stream = stream.unwrap();
        stream.read(pkt.input_buf).unwrap();

        pkt.extract_pkt();
    }
}
3 лайка

kpp, спасибо за информацию.
vesss, спасибо за информацию и пример.

vessd, я понимаю, что str::from_utf8(…) не выделяет память, а возвращает &str, поэтому его и использую.
У меня тоже была идея не хранить в стуктуре &str, а возвращать через метод. Но дело в том, что на самом деле мне нужен срез &str не всего буфера input_buf, а только содержимого самого принятого пакета, кроме того нужны &str на определенные данные (куски) внутри этого пакета. Пакет внутри буфера input_buf может быть принят любой длины. Мне бы хотелось сформировать все эти &str за раз в одном методе (который будет вызван в начале программы), и не тратить время на парсинг пакета в середине программы.

Не хотел использовать static-переменную, но ничего другого не придумал.
Так не слишком отстойно будет?

И все-таки, для общего понимания, какое время жизни у массива input_buf в моей первой реализации?

use std::str;
use std::io::Write;
use std::io::Read;
use std::net::TcpListener;

pub static mut INPUT_BUF: [u8; 512] = [0u8; 512];
pub struct Packet
{
    len: Option<usize>, //Длина пакета в буфере
    pub src_packet: Option<&'static str>, //&str на пакет
    pub cs: Option<&'static str>,
}


impl Packet
{
    ///Конструктор
    pub fn new() -> Packet
    {
        Packet
        {
            len: None,
            src_packet: None,
            cs: None,
        }
    }

    ///Переинициализация
    unsafe fn reset() -> Packet
    {
        INPUT_BUF = [0u8; 512];
        Packet
        {
            len: None,
            src_packet: None,
            cs: None,
        }
    }

    unsafe fn extract_pkt(& mut self)
    {
        self.src_packet = str::from_utf8(&INPUT_BUF[0..self.len.unwrap()]).ok();
        self.cs = str::from_utf8(&INPUT_BUF[self.len.unwrap()-2..self.len.unwrap()]).ok();
    }
}


fn main()
{
    let mut pkt = Packet::new();

    let addr = "127.0.0.1:9999";
    let listener = TcpListener::bind(addr).unwrap();
    println!("Server listening at {}", addr);

    for stream in listener.incoming() //stream типа TcpStream
    {
        let mut stream = stream.unwrap();
        loop
        {
                unsafe{
                    pkt = Packet::reset();
                    pkt.len = stream.read(&mut INPUT_BUF).ok();
                    pkt.extract_pkt();
                };

                //Работа с данными из пакета...
        }
    }
}

То что вы хотите можно сделать без статических переменных:

use std::str;
use std::io::Read;
use std::net::TcpListener;


pub struct PacketStr<'a>
{
    pub src_packet: &'a str, //&str на пакет
    pub cs: &'a str,
}


impl<'a> PacketStr<'a>
{
    pub fn new(buf:&'a[u8], len:usize) -> Option<PacketStr<'a>>
    {
        //parse buf and get strings offsets
        let src_offset = 1..10;
        let cs_offset = 22..33;      
        Some(
            PacketStr
            {
                src_packet: str::from_utf8(&buf[src_offset]).ok()?,
                cs: str::from_utf8(&buf[cs_offset]).ok()?,
            }
        )
    }
}


fn main()
{

    let addr = "127.0.0.1:9999";
    let listener = TcpListener::bind(addr).unwrap();
    println!("Server listening at {}", addr);
    let mut buf = [0u8;512];
    let mut len = 0;
    for stream in listener.incoming() //stream типа TcpStream
    {
        let mut stream = stream.unwrap();
        loop
        {
            len = stream.read(&mut buf).expect("cant read from stream");
            let pkt_strs = PacketStr::new(&buf, len).expect("cant get strings");
            //Работа с данными из пакета...
        }
    }
}

Как начинающему могу посоветовать:

  1. Разберитесь как работают времена жизни в Rust;
  2. Никогда не используйте unsafe и мутабельные статики;

Если выполните п.1, у вас пропадет желание нарушать п.2.
Иногда без unsafe дейстивельно нельзя обойтись, но такое бывает очень редко.

Ваша первая реализация не работает. Нельзя хранить ссылку в той же структуре, где хранятся данные на которые вы ссылаетесь (подробнее см. ссылки в ответе @kpp).
Время жизни в объявлении структуры pub struct Packet<'a>{... - это что-то вроде обобщенного параметра, оно определяется компилятором при создании конкретного экземпляра.
У вас структура владеет массивом, так что время жизни массива равно времени жизни всей структуры Packet. Оно будет разным для каждого экземпляра структуры.

Указывать время жизни для него не требуется, это массив фиксированной длинны, который размещается на стеке.
В общем случае лучше использовать Vec.

Пожалуйста, откройте для себя tokio. В нем есть монстр, который может быть полезен: tokio::codec::length_delimited.

А вот тут прочитайте как думать каналами и как использовать tokio с его framed структурами: https://github.com/jgallagher/tokio-chat-example/blob/master/tokio-chat-server/src/main.rs

И упаси вас Феррис от написания сниппетов в 15 строк с тремя unsafe.

Вообще забудьте про оптимизацию, про эти reset/очищения структур и переиспользование.

1 лайк