Есть ли способ вернуть ссылку на переменную, созданную в функции?

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

Проблема в том, что OpenOptions.new().write() может потерпеть неудачу. В этом случае я хочу вызвать пользовательскую функцию trycreate(), Идея состоит в том, чтобы создать файл вместо его открытия и вернуть дескриптор. Поскольку имя файла является фиксированным, trycreate() не имеет аргументов, и я не могу установить время жизни возвращаемого значения.

Как я могу решить эту проблему?

use std::io::Write;
use std::fs::OpenOptions;
use std::path::Path;

fn trycreate() -> &OpenOptions {
    let f = OpenOptions::new().write(true).open("foo.txt");
    let mut f = match f {
        Ok(file)  => file,
        Err(_)  => panic!("ERR"),
    };
    f
}

fn main() {
    {
        let f = OpenOptions::new().write(true).open(b"foo.txt");
        let mut f = match f {
            Ok(file)  => file,
            Err(_)  => trycreate("foo.txt"),
        };
        let buf = b"test1\n";
        let _ret = f.write(buf).unwrap();
    }
    println!("50%");
    {
        let f = OpenOptions::new().append(true).open("foo.txt");
        let mut f = match f {
            Ok(file)  => file,
            Err(_)  => panic!("append"),
        };
        let buf = b"test2\n";
        let _ret = f.write(buf).unwrap();
    }
    println!("Ok");
}

5 ответов

fjh - это абсолютно правильно, но я хочу более подробно прокомментировать и коснуться некоторых других ошибок в вашем коде.

Есть ли способ вернуть ссылку из функции без аргументов?

Технически "да", но для того, что вы хотите, "нет".

Ссылка указывает на существующий кусок памяти. В функции без аргументов можно ссылаться только на глобальные константы (которые имеют время жизни &'static) и локальные переменные. Я пока проигнорирую глобалы.

В таком языке, как C или C++, вы можете взять ссылку на локальную переменную и вернуть ее. Однако, как только функция вернется, нет гарантии, что память, на которую вы ссылаетесь, останется той, о которой вы думали. Некоторое время он может оставаться тем, что вы ожидаете, но в конечном итоге память будет использоваться повторно для чего-то другого. Как только ваш код смотрит на память и пытается интерпретировать имя пользователя как сумму денег, оставшуюся на банковском счете, возникнут проблемы!

Это то, что предотвращает время жизни Rust - вам не разрешено использовать ссылку сверх того, как долго указанное значение действительно в текущей ячейке памяти.

Вместо того, чтобы пытаться вернуть ссылку, верните принадлежащий объект. String вместо &str, Vec<T> вместо &[T], T вместо &T, так далее.

Ваша актуальная проблема

Посмотрите документацию для OpenOptions::open:

fn open<P: AsRef<Path>>(&self, path: P) -> Result<File>

Возвращает Result<File> так что я не знаю, как вы ожидаете вернуть OpenOptions или ссылка на один. Ваша функция будет работать, если вы переписали ее как:

fn trycreate() -> File {
    OpenOptions::new().write(true).open("foo.txt").expect("Couldn't open")
}

Это использует Result::expect паниковать с полезным сообщением об ошибке. Конечно, паника в кишках вашей программы не очень полезна, поэтому рекомендуется распространять ваши ошибки обратно:

fn trycreate() -> io::Result<File> {
    OpenOptions::new().write(true).open("foo.txt")
}

А также Option а также Result есть много хороших методов для работы с цепочкой логики ошибок. Здесь вы можете использовать or_else:

let f = OpenOptions::new().write(true).open("foo.txt");
let mut f = f.or_else(|_| trycreate()).expect("failed at creating");

Я также создал бы "внутреннюю основу", которая возвращает Result, Все вместе, включая предложения FJH:

use std::io::{self, Write};
use std::fs::OpenOptions;

fn inner_main() -> io::Result<()> {
    let mut f = OpenOptions::new()
        .create(true)
        .write(true)
        .append(true)
        .open("foo.txt")?;

    f.write(b"test1\n")?;
    f.write(b"test2\n")?;
    Ok(())
}

fn main() {
    inner_main().expect("An error occurred");
    println!("Ok");
}

Есть ли способ вернуть ссылку из функции без аргументов?

Нет (кроме ссылок на статические значения, но они здесь не помогают).

Тем не менее, вы можете посмотреть на OpenOptions::create, Если вы измените свою первую строку в main в

let  f = OpenOptions::new().write(true).create(true).open(b"foo.txt");

файл будет создан, если он еще не существует, что должно решить вашу первоначальную проблему.

Ссылки - это указатели. После выполнения функции они извлекаются из стека выполнения, а ресурсы выделяются.

В следующем примере xсбрасывается в конце блока. После этого ссылка&xбудет указывать на некоторые данные мусора. По сути, это висячий указатель. Компилятор Rust не допускает этого, поскольку это небезопасно.

fn run() -> &u32 {
    let x: u32 = 42;

    return &x;
} // x is dropped here

fn main() {
    let x = run();
}

Это уточнение ответа snnsnn, в котором кратко объясняется проблема, но не слишком конкретно.

Rust не позволяет возвращать ссылку на переменную, созданную в функции. Есть ли обходной путь? Да, просто поместите эту переменную в Box и верните ее. Пример:

fn run() -> Box<u32> {
    let x: u32 = 42;
    return Box::new(x);
} 

fn main() {
    println!("{}", run());
}

код на детской площадке ржавчины

Как правило, чтобы избежать подобных проблем в Rust, возвращайте принадлежащий объект (Box, Vec, String, ...) вместо ссылки на переменную:

  • Box<T> вместо &T
  • Vec<T> вместо &[T]
  • String вместо &str

Для других типов обратитесь к Периодической таблице типов Rust, чтобы выяснить, какой принадлежащий объект использовать.

Конечно, в этом примере вы можете просто вернуть значение (T вместо &T или же Box<T>)

fn run() -> u32 {
    let x: u32 = 42;
    return x;
} 

Да!Но надо найти способ продлить жизнь. Один из способов сделать это — предоставить изменяемую ссылку на фиктивное значение/значение по умолчанию (&mut T) для функции, а затем заполнить/заменить значение в функции, а затем вернуть ссылку на это значение (&T). Таким образом вы можете указать время жизни, чтобы возвращаемая ссылка получала время жизни значения вне функции.

Примеры:

      //&mut T -> &T
fn example2<'a>(life: &'a mut Vec<i32>) -> &'a Vec<i32> {
    *life = vec![1, 2, 3, 4];
    life
}

fn test_example2() {
    //Could also use Vec::new()
    let mut life = Vec::default();
    let res = example2(&mut life);
    println!("{:?}", res)
}

fn test2_example2() {
    let life = &mut Vec::default();
    let res = example2(life);
    println!("{:?}", res)
}

//shows real use case
fn get_check_test_slices2<'a>(
    lifetime: &'a mut Vec<usize>,
    limit: usize,
) -> impl Iterator<Item = (&'a [usize], &'a [usize])> + 'a {
    *lifetime = primes1_iter_bitvec(limit).collect::<Vec<_>>();
    all_test_check_slices(lifetime)
}

Изменить: чем это отличается от простого предоставления &mut T функции? (вопрошает Chayim Friedman (см. старое решение ниже)): Это в основном то же самое... Ранее я проигрывал контролеру заимствований, и поэтому я не просто использовал &mut T. Но после возобновления битв мне, наконец, удалось просто используйте &mut T. Спасибо за проницательный вопрос.

Старое решение. Мое решение работает путем создания значения по умолчанию перед вызовом функции, которое позже функция заменяет/заполняет и возвращает ссылку.

      /// Used to return references to values created in a function.
/// fn example<'a>(lt:& 'a mut LifeExtender<Vec<i32>>) -> &'a Vec<i32> {
///     lt.set(vec![1,2,3,4]);
///     lt.get()
/// }
pub struct LifeExtender<T> {
    value: T,
}

impl<T> Default for LifeExtender<T>
where
    T: Default,
{
    /// using T default.
    pub fn default() -> Self {
        Self {
            value: T::default(),
        }
    }
}

impl<T> LifeExtender<T> {
    /// If T doesn't have default.
    pub fn new(value: T) -> Self {
        Self { value }
    }
    /// set value to be returned by reference
    pub fn set(&mut self, new_value: T) {
        self.value = new_value;
    }
    /// Get a reference with lifetime self.
    pub fn get<'a>(&'a self) -> &'a T {
        &self.value
    }
    /// Get a mut reference with lifetime self.
    pub fn get_mut<'a>(&'a mut self) -> &'a mut T {
        &mut self.value
    }
}

fn example<'a>(life: &'a mut LifeExtender<Vec<i32>>) -> &'a Vec<i32> {
    let local_value = vec![1, 2, 3, 4];
    life.set(local_value);
    life.get()
}

//prints: [1,2,3,4]
pub fn test_example() {
    let mut life = LifeExtender::default();
    let res = example(&mut life);
    println!("{:?}", res);
}

//Real example code snippet, where I used this solution: 
fn get_check_slices2<'a>(
    lifetime: &'a mut LifeExtender<Vec<usize>>,
    limit: usize,
) -> impl Iterator<Item = (&'a [usize], &'a [usize])> + 'a {
    lifetime.set(primes1_iter_bitvec(limit).collect::<Vec<_>>());
    all_test_check_slices(lifetime.get())
}
Другие вопросы по тегам