多次可变和不可变借用的最佳实践是什么?
What are the best practices for multiple mutable and immutable borrows?
我正在开发 returns Zip 存档中特定文件内容的功能。因为我知道文件的位置,所以我尝试使用 ZipArchive.by_name
方法访问它。但是,文件名可能以不同的大小写形式书写。如果发生这种情况 (FileNotFound
),我需要遍历存档中的所有文件并与模板执行不区分大小写的比较。但是,在这种情况下,我遇到了两个与借用有关的错误。
这是最小的示例(我使用 BarcodeScanner Android 应用程序 (../test_data/simple_apks/BarcodeScanner4.0.apk
),但您可以使用任何 apk 文件,只需替换它的路径即可。您可以下载一个,例如,在 ApkMirror):
use std::{error::Error, fs::File, path::Path};
const MANIFEST_MF_PATH: &str = "META-INF/MANIFEST.MF";
fn main() {
let apk_path = Path::new("../test_data/simple_apks/BarcodeScanner4.0.apk");
let _manifest_data = get_manifest_data(apk_path);
}
fn get_manifest_data(apk_path: &Path) -> Result<String, Box<dyn Error>> {
let f = File::open(apk_path)?;
let mut apk_as_archive = zip::ZipArchive::new(f)?;
let _manifest_entry = match apk_as_archive.by_name(MANIFEST_MF_PATH) {
Ok(manifest) => manifest,
Err(zip::result::ZipError::FileNotFound) => {
let manifest_entry = apk_as_archive
.file_names()
.find(|f| f.eq_ignore_ascii_case(MANIFEST_MF_PATH));
match manifest_entry {
Some(entry) => apk_as_archive.by_name(entry).unwrap(),
None => {
return Err(Box::new(zip::result::ZipError::FileNotFound));
}
}
}
Err(err) => {
return Err(Box::new(err));
}
};
Ok(String::new()) //stub
}
Cargo.toml
:
[package]
name = "multiple_borrows"
version = "0.1.0"
authors = ["Yury"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
zip = "^0.5.8"
错误如下:
error[E0502]: cannot borrow `apk_as_archive` as immutable because it is also borrowed as mutable
--> src/main.rs:17:34
|
14 | let _manifest_entry = match apk_as_archive.by_name(MANIFEST_MF_PATH) {
| ----------------------------------------
| |
| mutable borrow occurs here
| a temporary with access to the mutable borrow is created here ...
...
17 | let manifest_entry = apk_as_archive
| ^^^^^^^^^^^^^^ immutable borrow occurs here
...
31 | };
| - ... and the mutable borrow might be used here, when that temporary is dropped and runs the destructor for type `std::result::Result<ZipFile<'_>, ZipError>`
error[E0499]: cannot borrow `apk_as_archive` as mutable more than once at a time
--> src/main.rs:22:32
|
14 | let _manifest_entry = match apk_as_archive.by_name(MANIFEST_MF_PATH) {
| ----------------------------------------
| |
| first mutable borrow occurs here
| a temporary with access to the first borrow is created here ...
...
22 | Some(entry) => apk_as_archive.by_name(entry).unwrap(),
| ^^^^^^^^^^^^^^ second mutable borrow occurs here
...
31 | };
| - ... and the first borrow might be used here, when that temporary is dropped and runs the destructor for type `std::result::Result<ZipFile<'_>, ZipError>`
我知道这些错误与糟糕的架构决策有关。这种情况下的最佳做法是什么?
by_name()
returns 指向代表存档的对象内部的数据。同时,它需要一个 &mut
对存档的引用,大概是因为它需要在读取时更新一些内部数据结构。不幸的结果是您不能在调用 by_name()
的同时保留先前调用 by_name()
返回的数据。在您的情况下,继续访问存档的匹配项不包含对存档的引用,但借用检查器还不够智能,无法检测到。
通常的解决方法是分两步完成:首先,确定清单文件是否存在,然后通过名称检索或在 file_names()
中搜索。在后一种情况下,您将需要进行另一次拆分,这次是在再次调用 by_name()
之前克隆文件名。这是出于同样的原因:by_name()
需要对存档的可变引用,当您持有引用其中数据的文件名时无法获得该引用。克隆该名称会以 运行 时间成本(通常可忽略不计)创建一个新副本,从而允许第二次调用 by_name()
.
最后,您可以将 ok_or_else()
组合子与 ?
运算符结合使用以简化错误传播。应用所有这些后,函数可能如下所示:
fn get_manifest_data(apk_path: &Path) -> Result<String, Box<dyn Error>> {
let f = File::open(apk_path)?;
let mut apk_as_archive = zip::ZipArchive::new(f)?;
let not_found = matches!(
apk_as_archive.by_name(MANIFEST_MF_PATH),
Err(zip::result::ZipError::FileNotFound)
);
let _manifest_entry = if not_found {
let entry_name = apk_as_archive
.file_names()
.find(|f| f.eq_ignore_ascii_case(MANIFEST_MF_PATH))
.ok_or_else(|| zip::result::ZipError::FileNotFound)?
.to_owned();
apk_as_archive.by_name(&entry_name).unwrap()
} else {
apk_as_archive.by_name(MANIFEST_MF_PATH)?
};
Ok(String::new()) //stub
}
我正在开发 returns Zip 存档中特定文件内容的功能。因为我知道文件的位置,所以我尝试使用 ZipArchive.by_name
方法访问它。但是,文件名可能以不同的大小写形式书写。如果发生这种情况 (FileNotFound
),我需要遍历存档中的所有文件并与模板执行不区分大小写的比较。但是,在这种情况下,我遇到了两个与借用有关的错误。
这是最小的示例(我使用 BarcodeScanner Android 应用程序 (../test_data/simple_apks/BarcodeScanner4.0.apk
),但您可以使用任何 apk 文件,只需替换它的路径即可。您可以下载一个,例如,在 ApkMirror):
use std::{error::Error, fs::File, path::Path};
const MANIFEST_MF_PATH: &str = "META-INF/MANIFEST.MF";
fn main() {
let apk_path = Path::new("../test_data/simple_apks/BarcodeScanner4.0.apk");
let _manifest_data = get_manifest_data(apk_path);
}
fn get_manifest_data(apk_path: &Path) -> Result<String, Box<dyn Error>> {
let f = File::open(apk_path)?;
let mut apk_as_archive = zip::ZipArchive::new(f)?;
let _manifest_entry = match apk_as_archive.by_name(MANIFEST_MF_PATH) {
Ok(manifest) => manifest,
Err(zip::result::ZipError::FileNotFound) => {
let manifest_entry = apk_as_archive
.file_names()
.find(|f| f.eq_ignore_ascii_case(MANIFEST_MF_PATH));
match manifest_entry {
Some(entry) => apk_as_archive.by_name(entry).unwrap(),
None => {
return Err(Box::new(zip::result::ZipError::FileNotFound));
}
}
}
Err(err) => {
return Err(Box::new(err));
}
};
Ok(String::new()) //stub
}
Cargo.toml
:
[package]
name = "multiple_borrows"
version = "0.1.0"
authors = ["Yury"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
zip = "^0.5.8"
错误如下:
error[E0502]: cannot borrow `apk_as_archive` as immutable because it is also borrowed as mutable
--> src/main.rs:17:34
|
14 | let _manifest_entry = match apk_as_archive.by_name(MANIFEST_MF_PATH) {
| ----------------------------------------
| |
| mutable borrow occurs here
| a temporary with access to the mutable borrow is created here ...
...
17 | let manifest_entry = apk_as_archive
| ^^^^^^^^^^^^^^ immutable borrow occurs here
...
31 | };
| - ... and the mutable borrow might be used here, when that temporary is dropped and runs the destructor for type `std::result::Result<ZipFile<'_>, ZipError>`
error[E0499]: cannot borrow `apk_as_archive` as mutable more than once at a time
--> src/main.rs:22:32
|
14 | let _manifest_entry = match apk_as_archive.by_name(MANIFEST_MF_PATH) {
| ----------------------------------------
| |
| first mutable borrow occurs here
| a temporary with access to the first borrow is created here ...
...
22 | Some(entry) => apk_as_archive.by_name(entry).unwrap(),
| ^^^^^^^^^^^^^^ second mutable borrow occurs here
...
31 | };
| - ... and the first borrow might be used here, when that temporary is dropped and runs the destructor for type `std::result::Result<ZipFile<'_>, ZipError>`
我知道这些错误与糟糕的架构决策有关。这种情况下的最佳做法是什么?
by_name()
returns 指向代表存档的对象内部的数据。同时,它需要一个 &mut
对存档的引用,大概是因为它需要在读取时更新一些内部数据结构。不幸的结果是您不能在调用 by_name()
的同时保留先前调用 by_name()
返回的数据。在您的情况下,继续访问存档的匹配项不包含对存档的引用,但借用检查器还不够智能,无法检测到。
通常的解决方法是分两步完成:首先,确定清单文件是否存在,然后通过名称检索或在 file_names()
中搜索。在后一种情况下,您将需要进行另一次拆分,这次是在再次调用 by_name()
之前克隆文件名。这是出于同样的原因:by_name()
需要对存档的可变引用,当您持有引用其中数据的文件名时无法获得该引用。克隆该名称会以 运行 时间成本(通常可忽略不计)创建一个新副本,从而允许第二次调用 by_name()
.
最后,您可以将 ok_or_else()
组合子与 ?
运算符结合使用以简化错误传播。应用所有这些后,函数可能如下所示:
fn get_manifest_data(apk_path: &Path) -> Result<String, Box<dyn Error>> {
let f = File::open(apk_path)?;
let mut apk_as_archive = zip::ZipArchive::new(f)?;
let not_found = matches!(
apk_as_archive.by_name(MANIFEST_MF_PATH),
Err(zip::result::ZipError::FileNotFound)
);
let _manifest_entry = if not_found {
let entry_name = apk_as_archive
.file_names()
.find(|f| f.eq_ignore_ascii_case(MANIFEST_MF_PATH))
.ok_or_else(|| zip::result::ZipError::FileNotFound)?
.to_owned();
apk_as_archive.by_name(&entry_name).unwrap()
} else {
apk_as_archive.by_name(MANIFEST_MF_PATH)?
};
Ok(String::new()) //stub
}