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


#1

Имеется массив 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.
Или проблема со временем жизни всей структуры?

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


#2

Что я вижу, так это самоссылающуюся структуру. Почитай цикл статей про это, чтобы понять, что такое самоссылающиеся структуры, почему возникла проблема, и как это можно решить:
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’ов, без диких времен жизни.


#3

Думаю тебе не нужно хранить &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();
    }
}

#4

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();
                };

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

#5

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

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. Оно будет разным для каждого экземпляра структуры.


#6

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


#7

Пожалуйста, откройте для себя 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/очищения структур и переиспользование.