LINUX.ORG.RU

Создать и вернуть структуру из функции

 ,


0

2

Не могу я понять эти ваши лайфтаймы. Есть, к примеру, вот такие структуры:

struct A {
    i: i64,
}

struct B<'a> {
    a: &'a A,
}

Я хочу сделать функцию, которая бы создавала и возвращала экземпляр B, но получаю ошибку use of undeclared lifetime name:

fn create_b(i: i64) -> B<'a> { // Ошибка в этой строке
    let a = A {
        i: i,
    };

    let b = B {
        a: &a,
    };

    return b;
}

А если указать результат функции как fn create_b(i: i64) -> B, то ошибка missing lifetime specifier. Я примерно понимаю, почему возникает ошикба, но как исправить не знаю.

Переписать этот код я не могу - в реале я использую библиотеку в которой примерно похожие стуктуры.

Ты пытаешься вернуть ссылку на локальный стек функции. Так сделать не получится, не важно какие лайфтаймы ты напишешь.

mersinvald ★★★★★
()
Ответ на: комментарий от mersinvald

Как раз это я примерно и понимаю. Сейчас у меня сложилось мнение, что в Rust просто не возможно сделать порождающие функции, вместо этого нужно тупо копипастить почти что один и тот же код.

neversleep ★★
() автор топика

А что, malloc() в расте нету?)))

IvanR ★★★
()
Ответ на: комментарий от neversleep

Ничего ты не понимаешь. Ты пытаешься вернуть структуру с ссылкой на структуру, которая порождена в функции. Но структура, которая порождена в функции, будет уничтожена при выходе из функции. Так на что будет ссылаться твоя ссылка? На мусор? На расте можно делать порождающие функции, но не функции, порождающие ссылки на объекты в астрале.

Virtuos86 ★★★★★
()
Ответ на: комментарий от Virtuos86

Но структура, которая порождена в функции, будет уничтожена при выходе из функции

Exactly. Даже для C это элементарная задача, а тут, как я понял, такое можно «провернуть» только со структурами, у которые в полях простые типы.

В официальной документации, к слову, самый сложный пример - это структуры со строками https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html. (офигеть, просто!) Сорри, но я сгорел :(

neversleep ★★
() автор топика
Последнее исправление: neversleep (всего исправлений: 4)
Ответ на: комментарий от neversleep

Что значит, даже для Си? Си это портабельный асм, на нем всё можно сделать, вопрос трудозатрат и времени.

Ты лучше скажи, зачем тебе это надо. Судя по коду, ты на Расте писать умеешь от слова никак. Какие create_b, return b;?? Это всё не так делается на Расте ))

Virtuos86 ★★★★★
()
Ответ на: комментарий от neversleep

такое можно «провернуть» только со структурами, у которые в полях простые типы.

В таком случае у тебя функция будет возвращать по значению, а не по ссылке. Вернуть по ссылке объект на стеке просто нельзя, потому что фрейм пропадет после завершения функции и останется ссылка на мусор.

Если тебе очень хочется вернуть именно по ссылке, то нужно делать объект (который struct A внутри твоего struct B) на куче, а не на стеке. Внизу уже скинули Box, который делает именно это.

Например, так:

use std::boxed::Box;

struct A {
    i: i64,
}

struct B {
    a: Box<A>,
}

fn create_b(i: i64) -> B {

    let a = Box::new(A { i, });

    let b = B {
        a,
    };

    b
}

Если тебя устраивает утекающая память, то можно так:

use std::boxed::Box;

struct A {
    i: i64,
}

struct B<'a> {
    a: &'a A,
}

fn create_b<'a>(i: i64) -> B<'a> {

    let a = Box::<A>::leak(Box::new(A { i }));

    let b = B {
        a,
    };

    b
}

Но вообще обычно в таких случаях (когда ну очень нужно инициализировать объект по ссылке в функции, ради оптимизации например), то используют MaybeUninit: https://doc.rust-lang.org/stable/std/mem/union.MaybeUninit.html#out-pointers

balsoft ★★
()
Последнее исправление: balsoft (всего исправлений: 5)
Ответ на: комментарий от neversleep

Exactly. Даже для C это элементарная задача, а тут, как я понял, такое можно «провернуть» только со структурами, у которые в полях простые типы.

Это элементарный способ выстрелить себе в ногу.

Ты пытаешься вернуть объект содержащий ссылку на объект который будет уничтожен при выходе из функции. Как ты не можешь понять элементарных вещей?

WatchCat ★★★★★
()
Ответ на: комментарий от neversleep

Даже для C это элементарная задача

И, если что, в C вернуть указатель но function-local memory это UB. Может показаться, что это работает, но на самом деле нет!

Вот твой пример, переписанный 1-в-1 на C:

#include <stdio.h>
#include <stdlib.h>

struct A {
  int i;
};

struct B {
  struct A * a;
};

struct B create_b(int i) {
  struct A a = { i };

  struct B b = { &a };

  return b;
}

int main() {
  struct B b = create_b(10);

  printf("%d\n", b.a->i);

  void * foo = malloc(1000);

  printf("%d\n", b.a->i);

  free(foo);
}

Наблюдаем и наслаждаемся:

$ clang -O0 main.c -o main && ./main
10
0
$ clang main.c -o main && ./main
257812584
0
$ gcc -O0 main.c -o main && ./main
10
32615
$ gcc main.c -o main && ./main
0
0
$ ./main
1009871352
0
$ ./main
1940232856
0
$ ./main
-628805544
0
$ ./main
-1315733560
0
$ ./main
-998120824
0

Так что то что раст не дает тебе так сделать звучит как огромное преимущество.

balsoft ★★
()
Последнее исправление: balsoft (всего исправлений: 1)
Ответ на: комментарий от neversleep

Даже для C это элементарная задача

Всё тебе правильно говорят. В C это невозможная задача. Как и в любом другом языке. Читай что тебе пишет компилятор Rust.

https://gcc.godbolt.org/z/rob8fsfjz

fsb4000 ★★★★★
()
Ответ на: комментарий от balsoft

Спасибо за интересные примеры с Box и за помощь.

Когда я говорил про C, конечно же я имел ввиду, что это будет malloc/free. Наверное, мне нужно было выразиться точнее.

neversleep ★★
() автор топика
Ответ на: комментарий от neversleep

Если хочешь в расте как с malloc/free, то смотри мой пример с leak. Чтобы сделать free, придется использовать unsafe:

// For this to be safe, the memory used by `b.a` must have been 
// allocated in accordance with the memory layout used by Box.
// Also note that b must not be used after this function is called
unsafe fn drop_b<'a>(b: &mut B<'a>) {
    let a_ptr: *mut A = std::ptr::addr_of_mut!(*b.a);
    let a = Box::from_raw(a_ptr);
    drop(a);
    drop(b);
}

Вот полный пример: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=0a54de880977e60657cf37fddaffbc92

Но, пожалуйста, никогда не делай так!

balsoft ★★
()
Ответ на: комментарий от neversleep

Я не знаю rust, но когда-то знал С и вы же сравниваете разные вещи. В С точно так же нельзя вернуть валидную ссылку на объект со стека после выхода из области видимости создания. Вы бы выделили память и вернули уже ее адрес, правильно? Так и в расте, наверное, просто у них указатели по-другому называются.

cdshines ★★★★★
()
Ответ на: комментарий от neversleep
  1. Продемонмтрируте, пожалуйста, соотвествующий код на С. Основываясь на этом примере мне будет намного проще объяснить вам как исправить ваш Rust код.

  2. TL;DR используйте Box<A> вместо &'a A.

trex6 ★★★★★
()

можешь еще rc использовать

struct A {
    i: i64,
}
#[derive(Clone)]
struct B {
    a: Rc<Box<A>>,
}
us976
()
Ответ на: комментарий от trex6

Про Box уже посоветовали, чтобы развеять все сомнения, вот пример на C:

#include <stdio.h>
#include <stdlib.h>

struct A {
    int i;
};

struct B {
    struct A *a;
};

struct A* create_struct_a(int i) {
    struct A *a = (struct A*) malloc(sizeof(struct A));

    a->i = i;
    return a;
}

void free_struct_a(struct A *a) {
    free((void *)a);
}

struct B* create_struct_b(int i) {
   struct B *b = (struct B*) malloc(sizeof(struct B));

   b->a = create_struct_a(i);
   return b;
}

void free_struct_b(struct B *b) {
    free_struct_a(b->a);
    free(b);
}

int main()
{
    struct B *b = create_struct_b(123);

    printf("%d\n", b->a->i);

    free_struct_b(b);
    return 0;
}

Как я понимаю, в Rust деструкторы будут не нужны (надо получше ознакомиться с Box, и про то, в какой момент они уничтожаются, но вот судя по названию именно метода Box::leak, такие Boxes «сами по себе» не уничтожаются).

neversleep ★★
() автор топика
Последнее исправление: neversleep (всего исправлений: 1)
Ответ на: комментарий от neversleep

Как я понимаю, в Rust деструкторы будут не нужны (надо получше ознакомиться с Box, и про то, в какой момент они уничтожаются, но вот судя по названию именно метода Box::leak, такие Boxes «сами по себе» не уничтожаются).

Пример с leak, это пример извращения и делать так ну совсем не надо, как тебе и сказали. То что тебе надо, это обычный Box<T> или Option<Box<T>>.

Смотри rust cheat sheet

Ну или cheats.rs

WatchCat ★★★★★
()
Ответ на: комментарий от neversleep
use std::boxed::Box;

#[derive(Debug)]
struct A {
    i: i64,
}

#[derive(Debug)]
struct B {
    a: Box<A>,
}

fn create_a(i: i64) -> A {
    A { i }
}

fn create_a_boxed(i: i64) -> Box<A> {
    Box::new( A { i } )
}

fn create_b(i: i64) -> B {
    B {
        a: Box::new(create_a(i)),
    }
}

fn create_b_boxed(i: i64) -> B {
    B {
        a: create_a_boxed(i),
    }
}


fn main() {

    let b = create_b(10);
    
    println!("{:?}", b);
    println!("{:?}", b.a);
    println!("{:?}", b.a.i);
    
    let b = create_b_boxed(20);

    println!("{:?}", b);
    println!("{:?}", b.a);
    println!("{:?}", b.a.i);

}
WatchCat ★★★★★
()

Переписать этот код я не могу - в реале я использую библиотеку в которой примерно похожие стуктуры.

В твоём реальном коде как объявлено поле в объекте В? Если точно также как в примере (ссылка на А), тогда ты должен создать объект А снаружи и передать его аргументом в конструктор В.

filosofia
()
Ответ на: комментарий от neversleep
  1. Вместо malloc стоит использовать Box::new. Это именно то, что вы хотите.

  2. Box::leak использовать не стотоит как минимум до тех пор, пока вы не прочтёте и не поймёте nomicon. В иедале - никогда.

P.S. Пожалуйста дайте знать, если вам нужно набросать пример с Box::new без leak’ов.

trex6 ★★★★★
()
Ответ на: комментарий от neversleep

судя по названию именно метода Box::leak, такие Boxes «сами по себе» не уничтожаются

Box уничтожается сам по себе. Но ты хочешь не Box, а &mut на кучу, и его уничтожить автоматически не получится (Box в моём примере с leak используется просто как относительно удобный способ выделить память на куче и затем освободить её, то же самое можно было бы сделать руками с помощью std::alloc::{alloc,dealloc}). У меня есть подозрение, что тут какая-то XY проблема и лучше было бы, если бы ты просто скинул что за библиотеку ты используешь и чего именно ты хочешь добиться.

balsoft ★★
()
Ответ на: комментарий от neversleep

Вот тебе пример с ручным использованием alloc и dealloc, если уж настолько хочется:

use std::alloc::{alloc,dealloc,Layout};

#[derive(Debug)]
struct A {
    i: i64,
}

#[derive(Debug)]
struct B<'a> {
    a: &'a mut A,
}

fn create_b<'a>(i: i64) -> B<'a> {
    unsafe {
        let a: *mut A = alloc(Layout::new::<A>()) as *mut A;

        *a = A { i };

        B {
            a: a.as_mut().unwrap(),
        }
    }
}

unsafe fn drop_b<'a>(b: &mut B<'a>) {
    let a_ptr: *mut A = std::ptr::addr_of_mut!(*b.a);
    dealloc(a_ptr as *mut u8, Layout::new::<A>());
    drop(b);
}

fn main() {
    let mut b = create_b(10);

    println!("{:?}", b);

    unsafe { drop_b(&mut b); };
}

Но, ещё раз, не делай так! Раст дает тебе огромный набор инструментов для написания хорошего, безопасного кода. Пока ты не поймешь, что делаешь, слушайся компилятор и не пиши unsafe без надобности.

Скорее всего, на самом деле ты хочешь пушистый и безопасный Box.

balsoft ★★
()
Ответ на: комментарий от balsoft

Мне нравится, что ты вываливаешь на неофита куски из номикона и при этом призываешь его не использовать это. Кул стори, чо.

Virtuos86 ★★★★★
()
Ответ на: комментарий от Virtuos86

Позволяет ли раст так делать? Да.

Стоит ли так делать, когда не понимаешь что делаешь? Нет.

Вот и вся мысль.

balsoft ★★
()
Ответ на: комментарий от balsoft

Позволяет ли раст так делать? Да.

Стоит ли так делать, когда не понимаешь что делаешь? Нет.

Вот и вся мысль.

То есть, если он разберется, что он делает, он смело может всю эту черную магию наворачивать, чтобы было, как на Си? Кто-то запутался в преследуемых целях и в средствах, избираемых для их достижения. В случае ТС достаточно было остановиться на обращении его внимания на Box.

Virtuos86 ★★★★★
()
Ответ на: комментарий от Virtuos86

Делать так я не собираюсь, но ничего плохого в том, что я + ещё N лоровцев узнают о таких возможностях, не вижу.

Всем спасибо за помощь.

neversleep ★★
() автор топика
Последнее исправление: neversleep (всего исправлений: 1)
Ответ на: комментарий от neversleep

Нафиг мне эти ваши звёздочки не нужны, у меня они вообще скрыты стилями. Асиль хоть первые страницы растокниги прежде чем такую хрень элементарную спрашивать.

fulmar_lor
()
Ответ на: комментарий от neversleep

Забей, в раст-сообществе полно таких напыщенных «не таких как все» личностей. Используемый язык и его идеология элитизма и серебряной пули всё-таки накладывает отпечаток на их мышлении. Тебе предстоит с такими ещё не раз столкнуться.

goto-vlad
()
Ответ на: комментарий от fulmar_lor

Конечно, не заметил, ведь спешил оставить свой бесценный комментарий.

Так что «прежде чем такую хрень элементарнуюавторитетную спрашиватьписать», асиль хоть первыепоследние комментарии в топике.

neversleep ★★
() автор топика
Ответ на: комментарий от neversleep

Но кого волнует ТС и его проблемы? Мы же на лоре, чёрт побери!

Верно, каналья!

Да, всё стало ясно.

бесценный

авторитетную

Понятно, что-то сам себе выдумал в голове и теперь возмущаешься.

fulmar_lor
()
Ответ на: комментарий от fulmar_lor

Я тебя ещё раз спрашиваю: ты для чего в тему ворвался? Какую ценность несёт твой высер?

Понятно, что-то сам себе выдумал в голове и теперь возмущаешься.

Т.е, тебя открытым текстом обычно посылают? Вежливых намёков не понимашь?

neversleep ★★
() автор топика
Последнее исправление: neversleep (всего исправлений: 1)
Ответ на: комментарий от neversleep

Ладно, я был не прав. У меня сегодня плохое настроение и я плохо соображаю из-за недосыпов. Но в игнор я тебя пожалуй добавлю.

fulmar_lor
()
Последнее исправление: fulmar_lor (всего исправлений: 2)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.