Repository: Serial-ATA/lofty-rs
Pull request #512: Fix Rust Edition 2024 compatibility issues
Author: login: uklotzde
Created: 2025-03-29T22:28:54Z | Merged: 2025-03-30T06:28:45Z



Diff:
diff --git a/Cargo.toml b/Cargo.toml
index 964b2d082..2c9aa7747 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,24 +1,47 @@
 [workspace]
+# TODO: Switch to MSRV-aware resolver version "3" (Rust 1.84)
 resolver = "2"
+
 members = [
 	"lofty",
 	"lofty_attr",
 	"ogg_pager",
 	"fuzz",
+	"examples/custom_resolver",
 ]
 
+[workspace.package]
+edition = "2021"
+rust-version = "1.82"
+repository = "https://github.com/Serial-ATA/lofty-rs"
+license = "MIT OR Apache-2.0"
+
 [workspace.dependencies]
+lofty = { path = "lofty" }
+lofty_attr = { path = "lofty_attr" }
+ogg_pager = { path = "ogg_pager" }
+
 byteorder = "1.5.0"
 
 [workspace.lints.rust]
 missing_docs                   = "deny"
 rust_2018_idioms               = { level = "deny", priority = -1 }
+rust_2021_compatibility        = { level = "deny", priority = -1 }
+rust_2024_compatibility        = { level = "deny", priority = -1 }
 trivial_casts                  = "deny"
 trivial_numeric_casts          = "deny"
 unused_import_braces           = "deny"
 explicit_outlives_requirements = "deny"
 unknown_lints                  = "allow"
 
+# Opt-in to new drop order of Edition 2024.
+# TODO: Remove if no longer needed.
+tail_expr_drop_order           = "allow"
+if_let_rescope                 = "allow"
+
+# TODO: Remove after switching to Edition 2024.
+edition_2024_expr_fragment_specifier = "allow"
+
 [workspace.lints.clippy]
 dbg_macro                          = "forbid"
 string_to_string                   = "forbid"
diff --git a/doc/NEW_TAG.md b/doc/NEW_TAG.md
index 435d78023..21446f189 100644
--- a/doc/NEW_TAG.md
+++ b/doc/NEW_TAG.md
@@ -187,7 +187,7 @@ impl FooTag {
         self.items.push((key, value))
     }
 
-    pub fn remove(&mut self, key: &str) -> impl Iterator<Item=String> + '_ {
+    pub fn remove<'a>(&'a mut self, key: &str) -> impl Iterator<Item=String> + use<'a> {
         self.items.retain(|(k, _)| !k.eq_ignore_ascii_case(&key));
     }
 }
@@ -200,7 +200,7 @@ impl Accessor for FooTag {
     fn title(&self) -> Option<Cow<'_, str>> {
         self.get("TITLE")
     }
-    fn set_title(&mut self, value: String) { 
+    fn set_title(&mut self, value: String) {
         self.insert(String::from("TITLE"), value)
     }
     fn remove_title(&mut self) {
@@ -263,10 +263,10 @@ Afterwards, we just need to add it into the list of maps in the `gen_item_keys!`
 gen_item_keys!(
     MAPS => [
         // ...
-        
+
         [TagType::Foo, FOO_MAP]
     ];
-    
+
     // ...
 );
 ```
diff --git a/examples/custom_resolver/Cargo.toml b/examples/custom_resolver/Cargo.toml
index e7b6cfdb0..de10cfc1c 100644
--- a/examples/custom_resolver/Cargo.toml
+++ b/examples/custom_resolver/Cargo.toml
@@ -1,10 +1,11 @@
 [package]
 name = "custom_resolver"
 version = "0.1.0"
-edition = "2021"
+edition.workspace = true
+rust-version.workspace = true
 
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-lofty = { path = "../.." }
-lofty_attr = { path = "../../lofty_attr" }
\ No newline at end of file
+lofty.workspace = true
+lofty_attr.workspace = true
diff --git a/examples/tag_reader.rs b/examples/tag_reader.rs
index 12349a018..dc6be8ad2 100644
--- a/examples/tag_reader.rs
+++ b/examples/tag_reader.rs
@@ -9,9 +9,7 @@ fn main() {
 	let path_str = std::env::args().nth(1).expect("ERROR: No path specified!");
 	let path = Path::new(&path_str);
 
-	if !path.is_file() {
-		panic!("ERROR: Path is not a file!");
-	}
+	assert!(path.is_file(), "ERROR: Path is not a file!");
 
 	let tagged_file = Probe::open(path)
 		.expect("ERROR: Bad path provided!")
diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml
index dc46a9856..a7542bbd3 100644
--- a/fuzz/Cargo.toml
+++ b/fuzz/Cargo.toml
@@ -3,7 +3,8 @@ name = "lofty-fuzz"
 version = "0.0.0"
 authors = ["Automatically generated"]
 publish = false
-edition = "2018"
+edition.workspace = true
+rust-version.workspace = true
 
 [package.metadata]
 cargo-fuzz = true
diff --git a/lofty/Cargo.toml b/lofty/Cargo.toml
index 63714e0e0..0c5f31eca 100644
--- a/lofty/Cargo.toml
+++ b/lofty/Cargo.toml
@@ -2,14 +2,15 @@
 name        = "lofty"
 version     = "0.22.2"
 authors     = ["Serial <69764315+Serial-ATA@users.noreply.github.com>"]
-edition     = "2021"
-license     = "MIT OR Apache-2.0"
 description = "Audio metadata library"
-repository  = "https://github.com/Serial-ATA/lofty-rs"
 keywords    = ["tags", "audio", "metadata", "id3", "vorbis"]
 categories  = ["multimedia", "multimedia::audio", "parser-implementations"]
 readme      = "../README.md"
 include     = ["src", "LICENSE-APACHE", "LICENSE-MIT", "SUPPORTED_FORMATS.md"]
+edition.workspace = true
+rust-version.workspace = true
+repository.workspace = true
+license.workspace = true
 
 [dependencies]
 # Vorbis comments pictures
diff --git a/lofty/src/ape/tag/mod.rs b/lofty/src/ape/tag/mod.rs
index 649e60c75..4ef5cb69a 100644
--- a/lofty/src/ape/tag/mod.rs
+++ b/lofty/src/ape/tag/mod.rs
@@ -188,7 +188,7 @@ impl ApeTag {
 
 	fn split_num_pair(&self, key: &str) -> (Option<u32>, Option<u32>) {
 		if let Some(ApeItem {
-			value: ItemValue::Text(ref text),
+			value: ItemValue::Text(text),
 			..
 		}) = self.get(key)
 		{
@@ -295,7 +295,7 @@ impl Accessor for ApeTag {
 
 	fn year(&self) -> Option<u32> {
 		if let Some(ApeItem {
-			value: ItemValue::Text(ref text),
+			value: ItemValue::Text(text),
 			..
 		}) = self.get("Year")
 		{
diff --git a/lofty/src/error.rs b/lofty/src/error.rs
index 4b945e37c..9b172489d 100644
--- a/lofty/src/error.rs
+++ b/lofty/src/error.rs
@@ -196,7 +196,7 @@ impl Display for Id3v2ErrorKind {
 				f,
 				"Attempted to write an encrypted frame with an invalid method symbol ({symbol})"
 			),
-			Self::BadFrame(ref frame_id, frame_value) => write!(
+			Self::BadFrame(frame_id, frame_value) => write!(
 				f,
 				"Attempted to write an invalid frame. ID: \"{frame_id}\", Value: \"{frame_value}\"",
 			),
diff --git a/lofty/src/id3/v2/items/sync_text.rs b/lofty/src/id3/v2/items/sync_text.rs
index cd64aa78d..a62083cad 100644
--- a/lofty/src/id3/v2/items/sync_text.rs
+++ b/lofty/src/id3/v2/items/sync_text.rs
@@ -245,7 +245,7 @@ impl SynchronizedTextFrame<'_> {
 				data.write_u8(0)?;
 			}
 
-			for (time, ref text) in &self.content {
+			for (time, text) in &self.content {
 				data.write_all(&encode_text(text, self.encoding, true))?;
 				data.write_u32::<BigEndian>(*time)?;
 			}
diff --git a/lofty/src/id3/v2/tag.rs b/lofty/src/id3/v2/tag.rs
index 206026ff3..cbaa4aecb 100644
--- a/lofty/src/id3/v2/tag.rs
+++ b/lofty/src/id3/v2/tag.rs
@@ -251,7 +251,10 @@ impl Id3v2Tag {
 	/// assert_eq!(titles.next(), Some("Foo"));
 	/// assert_eq!(titles.next(), Some("Bar"));
 	/// ```
-	pub fn get_texts(&self, id: &FrameId<'_>) -> Option<impl Iterator<Item = &str>> {
+	pub fn get_texts<'a>(
+		&'a self,
+		id: &FrameId<'_>,
+	) -> Option<impl Iterator<Item = &'a str> + use<'a>> {
 		if let Some(Frame::Text(TextInformationFrame { value, .. })) = self.get(id) {
 			return Some(value.split(V4_MULTI_VALUE_SEPARATOR));
 		}
@@ -425,7 +428,10 @@ impl Id3v2Tag {
 	/// assert!(tag.is_empty());
 	/// # Ok(()) }
 	/// ```
-	pub fn remove(&mut self, id: &FrameId<'_>) -> impl Iterator<Item = Frame<'static>> + '_ {
+	pub fn remove<'a>(
+		&'a mut self,
+		id: &FrameId<'_>,
+	) -> impl Iterator<Item = Frame<'static>> + use<'a> {
 		// TODO: drain_filter
 		let mut split_idx = 0_usize;
 
@@ -515,7 +521,7 @@ impl Id3v2Tag {
 	}
 
 	fn split_num_pair(&self, id: &FrameId<'_>) -> (Option<u32>, Option<u32>) {
-		if let Some(Frame::Text(TextInformationFrame { ref value, .. })) = self.get(id) {
+		if let Some(Frame::Text(TextInformationFrame { value, .. })) = self.get(id) {
 			let mut split = value
 				.split(&[V4_MULTI_VALUE_SEPARATOR, NUMBER_PAIR_SEPARATOR][..])
 				.flat_map(str::parse::<u32>);
@@ -546,7 +552,7 @@ impl Id3v2Tag {
 	/// This will translate any numeric genre IDs to their textual equivalent.
 	/// ID3v2.4-style multi-value fields will be split as normal.
 	pub fn genres(&self) -> Option<impl Iterator<Item = &str>> {
-		if let Some(Frame::Text(TextInformationFrame { ref value, .. })) = self.get(&GENRE_ID) {
+		if let Some(Frame::Text(TextInformationFrame { value, .. })) = self.get(&GENRE_ID) {
 			return Some(GenresIter::new(value, false));
 		}
 
@@ -1091,7 +1097,7 @@ fn handle_tag_split(tag: &mut Tag, frame: &mut Frame<'_>) -> bool {
 		//       Maybe ItemKey could use a "TXXX:" prefix eventually, so we would store
 		//       "TXXX:MusicBrainz Album Id" instead of "MusicBrainz Album Id".
 		// Store TXXX/WXXX frames by their descriptions, rather than their IDs
-		Frame::UserText(ExtendedTextFrame {
+		&mut Frame::UserText(ExtendedTextFrame {
 			ref description,
 			ref content,
 			..
@@ -1106,7 +1112,7 @@ fn handle_tag_split(tag: &mut Tag, frame: &mut Frame<'_>) -> bool {
 
 			return FRAME_CONSUMED;
 		},
-		Frame::UserUrl(ExtendedUrlFrame {
+		&mut Frame::UserUrl(ExtendedUrlFrame {
 			ref description,
 			ref content,
 			..
@@ -1122,7 +1128,7 @@ fn handle_tag_split(tag: &mut Tag, frame: &mut Frame<'_>) -> bool {
 			return FRAME_CONSUMED;
 		},
 
-		Frame::UniqueFileIdentifier(UniqueFileIdentifierFrame {
+		&mut Frame::UniqueFileIdentifier(UniqueFileIdentifierFrame {
 			ref owner,
 			ref identifier,
 			..
@@ -1179,7 +1185,7 @@ fn handle_tag_split(tag: &mut Tag, frame: &mut Frame<'_>) -> bool {
 		},
 
 		Frame::Picture(AttachedPictureFrame {
-			ref mut picture, ..
+			picture, ..
 		}) => {
 			tag.push_picture(std::mem::replace(picture, TOMBSTONE_PICTURE));
 			return FRAME_CONSUMED;
@@ -1216,7 +1222,7 @@ fn handle_tag_split(tag: &mut Tag, frame: &mut Frame<'_>) -> bool {
 		},
 		Frame::Url(UrlLinkFrame {
 			header: FrameHeader {id, .. },
-			ref mut content, ..
+			content, ..
 		}) => {
 			let item_key = ItemKey::from_key(TagType::Id3v2, id.as_str());
 			tag.items.push(TagItem::new(
@@ -1299,10 +1305,10 @@ impl MergeTag for SplitTagRemainder {
 			content: String,
 		}
 
-		fn join_language_items(
-			tag: &mut Tag,
+		fn join_language_items<'a>(
+			tag: &'a mut Tag,
 			key: &ItemKey,
-		) -> Option<impl Iterator<Item = JoinedLanguageItem>> {
+		) -> Option<impl Iterator<Item = JoinedLanguageItem> + use<'a>> {
 			let mut items = tag.take(key).collect::<Vec<_>>();
 			if items.is_empty() {
 				return None;
@@ -1489,7 +1495,7 @@ impl MergeTag for SplitTagRemainder {
 			let existing_tipl = merged.take_first(&FrameId::Valid(Cow::Borrowed("TIPL")));
 			if let Some(mut tipl_frame) = existing_tipl {
 				if let Frame::KeyValue(KeyValueFrame {
-					key_value_pairs: ref mut existing,
+					key_value_pairs: existing,
 					..
 				}) = &mut tipl_frame
 				{
diff --git a/lofty/src/id3/v2/write/frame.rs b/lofty/src/id3/v2/write/frame.rs
index 4feb0fa55..fc948bb12 100644
--- a/lofty/src/id3/v2/write/frame.rs
+++ b/lofty/src/id3/v2/write/frame.rs
@@ -69,7 +69,7 @@ where
 			// TDRC (Recording time) gets split into three frames: TYER, TDAT, and TIME
 			"TDOR" | "TDRC" => {
 				let mut value = frame.0.clone();
-				let Frame::Timestamp(ref mut f) = value.to_mut() else {
+				let Frame::Timestamp(f) = value.to_mut() else {
 					log::warn!("Discarding frame: {}, not supported in ID3v2.3", id);
 					continue;
 				};
@@ -139,7 +139,7 @@ where
 			// components in parentheses
 			"TCON" => {
 				let mut value = frame.0.clone();
-				let Frame::Text(ref mut f) = value.to_mut() else {
+				let Frame::Text(f) = value.to_mut() else {
 					log::warn!("Discarding frame: {}, not supported in ID3v2.3", id);
 					continue;
 				};
@@ -168,7 +168,7 @@ where
 			"TIPL" | "TMCL" => {
 				let mut value = frame.0.clone();
 				let Frame::KeyValue(KeyValueFrame {
-					ref mut key_value_pairs,
+					key_value_pairs,
 					encoding,
 					..
 				}) = value.to_mut()
diff --git a/lofty/src/mp4/ilst/atom.rs b/lofty/src/mp4/ilst/atom.rs
index b4d0a17e9..02c854d35 100644
--- a/lofty/src/mp4/ilst/atom.rs
+++ b/lofty/src/mp4/ilst/atom.rs
@@ -161,7 +161,7 @@ impl<'a> Atom<'a> {
 	/// );
 	/// assert_eq!(atom.into_data().count(), 1);
 	/// ```
-	pub fn into_data(self) -> impl Iterator<Item = AtomData> {
+	pub fn into_data(self) -> impl Iterator<Item = AtomData> + use<> {
 		self.data.into_iter()
 	}
 
diff --git a/lofty/src/mp4/ilst/mod.rs b/lofty/src/mp4/ilst/mod.rs
index b01508be1..eb9f5a7ea 100644
--- a/lofty/src/mp4/ilst/mod.rs
+++ b/lofty/src/mp4/ilst/mod.rs
@@ -251,7 +251,10 @@ impl Ilst {
 	/// let title = ilst.get(&TITLE_IDENTIFIER);
 	/// assert!(title.is_none());
 	/// ```
-	pub fn remove(&mut self, ident: &AtomIdent<'_>) -> impl Iterator<Item = Atom<'static>> + '_ {
+	pub fn remove<'a>(
+		&'a mut self,
+		ident: &AtomIdent<'_>,
+	) -> impl Iterator<Item = Atom<'static>> + use<'a> {
 		// TODO: drain_filter
 		let mut split_idx = 0_usize;
 
@@ -685,7 +688,7 @@ impl SplitTag for Ilst {
 					code: DataType::Reserved,
 					data,
 				} if Vec::len(data) >= 6 => {
-					if let AtomIdent::Fourcc(ref fourcc) = ident {
+					if let &mut AtomIdent::Fourcc(ref fourcc) = ident {
 						match fourcc {
 							b"trkn" => {
 								let current = u16::from_be_bytes([data[2], data[3]]);
diff --git a/lofty/src/mp4/ilst/write.rs b/lofty/src/mp4/ilst/write.rs
index e673ad5ec..2aa3cdef9 100644
--- a/lofty/src/mp4/ilst/write.rs
+++ b/lofty/src/mp4/ilst/write.rs
@@ -678,11 +678,11 @@ where
 		match value {
 			AtomData::UTF8(text) => write_data(DataType::Utf8, text.as_bytes(), writer)?,
 			AtomData::UTF16(text) => write_data(DataType::Utf16, text.as_bytes(), writer)?,
-			AtomData::Picture(ref pic) => write_picture(pic, writer)?,
+			AtomData::Picture(pic) => write_picture(pic, writer)?,
 			AtomData::SignedInteger(int) => write_signed_int(*int, writer)?,
 			AtomData::UnsignedInteger(uint) => write_unsigned_int(*uint, writer)?,
 			AtomData::Bool(b) => write_bool(*b, writer)?,
-			AtomData::Unknown { code, ref data } => write_data(*code, data, writer)?,
+			AtomData::Unknown { code, data } => write_data(*code, data, writer)?,
 		};
 	}
 
diff --git a/lofty/src/ogg/tag.rs b/lofty/src/ogg/tag.rs
index cb36ed82b..75a56cc81 100644
--- a/lofty/src/ogg/tag.rs
+++ b/lofty/src/ogg/tag.rs
@@ -158,7 +158,7 @@ impl VorbisComments {
 	/// // We've taken all the items
 	/// assert!(vorbis_comments.is_empty());
 	/// ```
-	pub fn take_items(&mut self) -> impl ExactSizeIterator<Item = (String, String)> {
+	pub fn take_items(&mut self) -> impl ExactSizeIterator<Item = (String, String)> + use<> {
 		let items = std::mem::take(&mut self.items);
 		items.into_iter()
 	}
@@ -287,7 +287,7 @@ impl VorbisComments {
 	/// 	println!("We removed the title: {title}");
 	/// }
 	/// ```
-	pub fn remove(&mut self, key: &str) -> impl Iterator<Item = String> + '_ {
+	pub fn remove<'a>(&'a mut self, key: &str) -> impl Iterator<Item = String> + use<'a> {
 		// TODO: drain_filter
 		let mut split_idx = 0_usize;
 
diff --git a/lofty/src/tag/item.rs b/lofty/src/tag/item.rs
index 7dbcc309e..4ae1778d6 100644
--- a/lofty/src/tag/item.rs
+++ b/lofty/src/tag/item.rs
@@ -472,7 +472,7 @@ macro_rules! gen_item_keys {
 					_ => {}
 				}
 
-				if let ItemKey::Unknown(ref unknown) = self {
+				if let ItemKey::Unknown(unknown) = self {
 					if allow_unknown {
 						return Some(unknown)
 					}
@@ -736,7 +736,7 @@ impl ItemValue {
 	/// Returns the value if the variant is `Text`
 	pub fn text(&self) -> Option<&str> {
 		match self {
-			Self::Text(ref text) => Some(text),
+			Self::Text(text) => Some(text),
 			_ => None,
 		}
 	}
@@ -744,7 +744,7 @@ impl ItemValue {
 	/// Returns the value if the variant is `Locator`
 	pub fn locator(&self) -> Option<&str> {
 		match self {
-			Self::Locator(ref locator) => Some(locator),
+			Self::Locator(locator) => Some(locator),
 			_ => None,
 		}
 	}
@@ -752,7 +752,7 @@ impl ItemValue {
 	/// Returns the value if the variant is `Binary`
 	pub fn binary(&self) -> Option<&[u8]> {
 		match self {
-			Self::Binary(ref bin) => Some(bin),
+			Self::Binary(bin) => Some(bin),
 			_ => None,
 		}
 	}
diff --git a/lofty/src/tag/mod.rs b/lofty/src/tag/mod.rs
index 36140ca44..8118936bb 100644
--- a/lofty/src/tag/mod.rs
+++ b/lofty/src/tag/mod.rs
@@ -406,7 +406,7 @@ impl Tag {
 	/// Removes all items with the specified [`ItemKey`], and returns them
 	///
 	/// See also: [take_filter()](Self::take_filter)
-	pub fn take(&mut self, key: &ItemKey) -> impl Iterator<Item = TagItem> + '_ {
+	pub fn take<'a>(&'a mut self, key: &ItemKey) -> impl Iterator<Item = TagItem> + use<'a> {
 		self.take_filter(key, |_| true)
 	}
 
@@ -444,11 +444,14 @@ impl Tag {
 	/// // The comments that didn't match the filter are still present.
 	/// assert_eq!(tag.get_strings(&ItemKey::Comment).count(), 1);
 	/// ```
-	pub fn take_filter(
-		&mut self,
+	pub fn take_filter<'a, F>(
+		&'a mut self,
 		key: &ItemKey,
-		mut filter: impl FnMut(&TagItem) -> bool,
-	) -> impl Iterator<Item = TagItem> + '_ {
+		mut filter: F,
+	) -> impl Iterator<Item = TagItem> + use<'a, F>
+	where
+		F: FnMut(&TagItem) -> bool,
+	{
 		// TODO: drain_filter
 		let mut split_idx = 0;
 
@@ -464,7 +467,7 @@ impl Tag {
 	}
 
 	/// Removes all items with the specified [`ItemKey`], and filters them through [`ItemValue::into_string`]
-	pub fn take_strings(&mut self, key: &ItemKey) -> impl Iterator<Item = String> + '_ {
+	pub fn take_strings<'a>(&'a mut self, key: &ItemKey) -> impl Iterator<Item = String> + use<'a> {
 		self.take(key).filter_map(|i| i.item_value.into_string())
 	}
 
diff --git a/lofty/tests/files/util/mod.rs b/lofty/tests/files/util/mod.rs
index 8fd12fb4a..4fdd2841c 100644
--- a/lofty/tests/files/util/mod.rs
+++ b/lofty/tests/files/util/mod.rs
@@ -15,7 +15,7 @@ macro_rules! temp_file {
 #[macro_export]
 macro_rules! no_tag_test {
 	($path:literal) => {{
-		let mut file = crate::temp_file!($path);
+		let mut file = $crate::temp_file!($path);
 		let tagged_file = lofty::probe::Probe::new(&mut file)
 			.options(lofty::config::ParseOptions::new().read_tags(false))
 			.guess_file_type()
@@ -27,7 +27,7 @@ macro_rules! no_tag_test {
 	(@MANDATORY_TAG $path:literal, expected_len: $expected_len:literal) => {{
 		use lofty::tag::TagExt as _;
 
-		let mut file = crate::temp_file!($path);
+		let mut file = $crate::temp_file!($path);
 		let tagged_file = lofty::probe::Probe::new(&mut file)
 			.options(lofty::config::ParseOptions::new().read_tags(false))
 			.guess_file_type()
@@ -43,7 +43,7 @@ macro_rules! no_tag_test {
 #[macro_export]
 macro_rules! no_properties_test {
 	($path:literal) => {{
-		let mut file = crate::temp_file!($path);
+		let mut file = $crate::temp_file!($path);
 		let tagged_file = lofty::probe::Probe::new(&mut file)
 			.options(lofty::config::ParseOptions::new().read_properties(false))
 			.guess_file_type()
diff --git a/lofty_attr/Cargo.toml b/lofty_attr/Cargo.toml
index e7bf1ee10..5e94271f5 100644
--- a/lofty_attr/Cargo.toml
+++ b/lofty_attr/Cargo.toml
@@ -2,12 +2,13 @@
 name = "lofty_attr"
 version = "0.11.1"
 authors = ["Serial <69764315+Serial-ATA@users.noreply.github.com>"]
-edition = "2021"
-license = "MIT OR Apache-2.0"
 description = "Macros for Lofty"
-repository = "https://github.com/Serial-ATA/lofty-rs"
 readme = "README.md"
 include = ["src", "Cargo.toml", "../LICENSE-*"]
+edition.workspace = true
+rust-version.workspace = true
+repository.workspace = true
+license.workspace = true
 
 [dependencies]
 syn = { version = "2.0.72", features = ["full", "parsing"] }
diff --git a/lofty_attr/src/lofty_file.rs b/lofty_attr/src/lofty_file.rs
index 02cea32c9..d21b6cabf 100644
--- a/lofty_attr/src/lofty_file.rs
+++ b/lofty_attr/src/lofty_file.rs
@@ -393,7 +393,7 @@ fn get_getters<'a>(
 fn generate_audiofile_impl(file: &LoftyFile) -> syn::Result<proc_macro2::TokenStream> {
 	fn tag_exists_iter(
 		tag_fields: &[FieldContents],
-	) -> impl Iterator<Item = proc_macro2::TokenStream> + '_ {
+	) -> impl Iterator<Item = proc_macro2::TokenStream> + use<'_> {
 		tag_fields.iter().map(|f| {
 			let name = &f.name;
 			if f.needs_option {
diff --git a/ogg_pager/Cargo.toml b/ogg_pager/Cargo.toml
index c2a3258bb..c1aba9a4f 100644
--- a/ogg_pager/Cargo.toml
+++ b/ogg_pager/Cargo.toml
@@ -2,13 +2,14 @@
 name = "ogg_pager"
 version = "0.7.0"
 authors = ["Serial <69764315+Serial-ATA@users.noreply.github.com>"]
-edition = "2021"
-license = "MIT OR Apache-2.0"
 description = "A simple OGG page reader"
-repository = "https://github.com/Serial-ATA/lofty-rs"
 keywords = ["ogg", "xiph"]
 categories = ["multimedia", "multimedia::audio", "parser-implementations"]
 include = ["src", "Cargo.toml", "../LICENSE-*"]
+edition.workspace = true
+rust-version.workspace = true
+repository.workspace = true
+license.workspace = true
 
 [dependencies]
 byteorder = { workspace = true }


Reviews:
user: login: Serial-ATA
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-30T01:02:49Z
user: login: uklotzde
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-30T02:25:53Z
user: login: Serial-ATA
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-30T02:29:53Z
user: login: uklotzde
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-30T03:04:31Z

Review comments:
diff_hunk: @@ -1,24 +1,38 @@
 [workspace]
 resolver = "2"
+
 members = [
 	"lofty",
 	"lofty_attr",
 	"ogg_pager",
 	"fuzz",
 ]
 
+[workspace.package]
+rust-version = "1.82"
+
 [workspace.dependencies]
 byteorder = "1.5.0"
 
 [workspace.lints.rust]
 missing_docs                   = "deny"
 rust_2018_idioms               = { level = "deny", priority = -1 }
+rust_2021_compatibility        = { level = "deny", priority = -1 }
+rust_2024_compatibility        = { level = "deny", priority = -1 }
 trivial_casts                  = "deny"
 trivial_numeric_casts          = "deny"
 unused_import_braces           = "deny"
 explicit_outlives_requirements = "deny"
 unknown_lints                  = "allow"
 
+# Opt-in to new drop order of Edition 2024.
+# TODO: Remove if no longer needed.
+tail_expr_drop_order           = "allow"
+if_let_rescope                 = "allow"
+
+# TODO: Remove after switching to Edition 2024.
+edition_2024_expr_fragment_specifier = "allow"
path: Cargo.toml
user: login: Serial-ATA
body: Are these still needed?
created_at: 2025-03-30T01:02:49Z
author_association: OWNER
diff_hunk: @@ -1,24 +1,38 @@
 [workspace]
 resolver = "2"
+
 members = [
 	"lofty",
 	"lofty_attr",
 	"ogg_pager",
 	"fuzz",
 ]
 
+[workspace.package]
+rust-version = "1.82"
+
 [workspace.dependencies]
 byteorder = "1.5.0"
 
 [workspace.lints.rust]
 missing_docs                   = "deny"
 rust_2018_idioms               = { level = "deny", priority = -1 }
+rust_2021_compatibility        = { level = "deny", priority = -1 }
+rust_2024_compatibility        = { level = "deny", priority = -1 }
 trivial_casts                  = "deny"
 trivial_numeric_casts          = "deny"
 unused_import_braces           = "deny"
 explicit_outlives_requirements = "deny"
 unknown_lints                  = "allow"
 
+# Opt-in to new drop order of Edition 2024.
+# TODO: Remove if no longer needed.
+tail_expr_drop_order           = "allow"
+if_let_rescope                 = "allow"
+
+# TODO: Remove after switching to Edition 2024.
+edition_2024_expr_fragment_specifier = "allow"
path: Cargo.toml
user: login: uklotzde
body: Yes. Only after switching to `edition = "2024` both `if_let_rescope` and `edition_2024_expr_fragment_specifier` will probably become obsolete.
created_at: 2025-03-30T02:25:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,24 +1,38 @@
 [workspace]
 resolver = "2"
+
 members = [
 	"lofty",
 	"lofty_attr",
 	"ogg_pager",
 	"fuzz",
 ]
 
+[workspace.package]
+rust-version = "1.82"
+
 [workspace.dependencies]
 byteorder = "1.5.0"
 
 [workspace.lints.rust]
 missing_docs                   = "deny"
 rust_2018_idioms               = { level = "deny", priority = -1 }
+rust_2021_compatibility        = { level = "deny", priority = -1 }
+rust_2024_compatibility        = { level = "deny", priority = -1 }
 trivial_casts                  = "deny"
 trivial_numeric_casts          = "deny"
 unused_import_braces           = "deny"
 explicit_outlives_requirements = "deny"
 unknown_lints                  = "allow"
 
+# Opt-in to new drop order of Edition 2024.
+# TODO: Remove if no longer needed.
+tail_expr_drop_order           = "allow"
+if_let_rescope                 = "allow"
+
+# TODO: Remove after switching to Edition 2024.
+edition_2024_expr_fragment_specifier = "allow"
path: Cargo.toml
user: login: Serial-ATA
body: You can bump to 2024 edition in this PR if you want. I don't think it's a breaking change.
created_at: 2025-03-30T02:29:53Z
author_association: OWNER
diff_hunk: @@ -1,24 +1,38 @@
 [workspace]
 resolver = "2"
+
 members = [
 	"lofty",
 	"lofty_attr",
 	"ogg_pager",
 	"fuzz",
 ]
 
+[workspace.package]
+rust-version = "1.82"
+
 [workspace.dependencies]
 byteorder = "1.5.0"
 
 [workspace.lints.rust]
 missing_docs                   = "deny"
 rust_2018_idioms               = { level = "deny", priority = -1 }
+rust_2021_compatibility        = { level = "deny", priority = -1 }
+rust_2024_compatibility        = { level = "deny", priority = -1 }
 trivial_casts                  = "deny"
 trivial_numeric_casts          = "deny"
 unused_import_braces           = "deny"
 explicit_outlives_requirements = "deny"
 unknown_lints                  = "allow"
 
+# Opt-in to new drop order of Edition 2024.
+# TODO: Remove if no longer needed.
+tail_expr_drop_order           = "allow"
+if_let_rescope                 = "allow"
+
+# TODO: Remove after switching to Edition 2024.
+edition_2024_expr_fragment_specifier = "allow"
path: Cargo.toml
user: login: uklotzde
body: Switching to Edition 2024 requires to reformat many source files.

I will prepare an intermediate step with some workspace cleanup as part of this PR. Switching editions should be done separately.
created_at: 2025-03-30T03:04:31Z
author_association: CONTRIBUTOR

PR comments:
user: login: uklotzde
created_at: 2025-03-29T22:32:50Z
body: @Serial-ATA What about the failing Style check that seems to be unrelated?
author_association: CONTRIBUTOR
user: login: Serial-ATA
created_at: 2025-03-30T01:00:06Z
body: > @Serial-ATA What about the failing Style check that seems to be unrelated?

I was able to format it on my end. CI uses the latest stable, maybe yours is out of date if you don't see the same formatting?
author_association: OWNER
user: login: uklotzde
created_at: 2025-03-30T02:33:00Z
body: > > @Serial-ATA What about the failing Style check that seems to be unrelated?
> 
> I was able to format it on my end. CI uses the latest stable, maybe yours is out of date if you don't see the same formatting?

I forgot to run `cargo fmt` manually.

I am used to `pre-commit` for running various checks. Code formatting is only one of them. Produces the same results locally and on CI. At least if the toolchains are in-sync.
author_association: CONTRIBUTOR
user: login: Serial-ATA
created_at: 2025-03-30T06:28:31Z
body: Thanks!
author_association: OWNER