Есть ли способ вернуть ссылку на переменную, созданную в функции?
Я хочу написать программу, которая запишет файл в 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())
}