From a3a426835e2ae153070a937e5aba844a939eba05 Mon Sep 17 00:00:00 2001
From: Iago Bonnici <iago.bonnici@umontpellier.fr>
Date: Mon, 10 Mar 2025 11:12:25 +0100
Subject: [PATCH] =?UTF-8?q?=E2=AC=87=EF=B8=8F=20Downgrade=20to=20build=20o?=
 =?UTF-8?q?n=20stable.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 rust-toolchain.toml  |  2 ++
 src/from_unstable.rs | 53 ++++++++++++++++++++++++++++++++++++++++++++
 src/lib.rs           | 13 +++++------
 src/parse.rs         |  2 +-
 src/parse/modules.rs |  6 ++---
 src/parse/samples.rs |  3 ++-
 6 files changed, 66 insertions(+), 13 deletions(-)
 create mode 100644 rust-toolchain.toml
 create mode 100644 src/from_unstable.rs

diff --git a/rust-toolchain.toml b/rust-toolchain.toml
new file mode 100644
index 0000000..292fe49
--- /dev/null
+++ b/rust-toolchain.toml
@@ -0,0 +1,2 @@
+[toolchain]
+channel = "stable"
diff --git a/src/from_unstable.rs b/src/from_unstable.rs
new file mode 100644
index 0000000..d1abdfb
--- /dev/null
+++ b/src/from_unstable.rs
@@ -0,0 +1,53 @@
+//! Work around features missing from stable.
+
+use arrayvec::ArrayVec;
+
+//--------------------------------------------------------------------------------------------------
+// std::array::try_from_fn
+
+pub(crate) fn try_from_fn<T, E, const N: usize>(
+    mut f: impl FnMut(usize) -> Result<T, E>,
+) -> Result<[T; N], E> {
+    // Use small stack-allocated vector as temporary initializer
+    let mut res = ArrayVec::<T, N>::new();
+    for i in 0..N {
+        let next = f(i)?;
+        res.push(next);
+    }
+    Ok(res.into_inner().unwrap_or_else(|_| unreachable!())) // (to avoid T: Debug)
+}
+
+//--------------------------------------------------------------------------------------------------
+// std::array::try_map
+
+pub(crate) trait TryMap<I, const N: usize>: IntoIterator<Item = I> + Sized {
+    fn try_map<E, O>(self, f: impl FnMut(I) -> Result<O, E>) -> Result<[O; N], E>;
+}
+
+impl<I, const N: usize> TryMap<I, N> for [I; N] {
+    fn try_map<E, O>(self, f: impl FnMut(I) -> Result<O, E>) -> Result<[O; N], E> {
+        let mut f = f;
+        // Same strategy as above.
+        let mut res = ArrayVec::<O, N>::new();
+        for a in self {
+            let next = f(a)?;
+            res.push(next);
+        }
+        Ok(res.into_inner().unwrap_or_else(|_| unreachable!()))
+    }
+}
+
+//--------------------------------------------------------------------------------------------------
+// std::slice::split_once (take this opportunity to simplify for our use case)
+
+pub(crate) trait SplitOnce<T> {
+    type Chunk;
+    fn split_once(self, pred: T) -> Option<(Self::Chunk, Self::Chunk)>;
+}
+impl<'s> SplitOnce<u8> for &'s [u8] {
+    type Chunk = &'s [u8];
+    fn split_once(self, pred: u8) -> Option<(Self::Chunk, Self::Chunk)> {
+        let index = self.iter().position(|&c| c == pred)?;
+        Some((&self[..index], &self[index + 1..]))
+    }
+}
diff --git a/src/lib.rs b/src/lib.rs
index a4e359b..1280265 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,9 +1,3 @@
-#![feature(array_try_from_fn)]
-#![feature(array_try_map)]
-#![feature(buf_read_has_data_left)]
-#![feature(generic_arg_infer)]
-#![feature(slice_split_once)]
-
 use std::{
     array,
     collections::HashMap,
@@ -23,6 +17,7 @@ use colored::Colorize;
 pub use config::Config;
 use demux::Reference;
 use flate2::{read::MultiGzDecoder, write::GzEncoder, Compression};
+use from_unstable::{self as fun, TryMap};
 use indexmap::{map::Entry, IndexMap};
 use io::{Context, ContextualizedReader, ContextualizedWriter};
 use num_format::{Locale, ToFormattedString};
@@ -39,6 +34,7 @@ use crate::fastq::Block;
 pub mod config;
 mod demux;
 mod dispatcher;
+mod from_unstable;
 mod io;
 mod parse;
 mod reader;
@@ -70,7 +66,7 @@ pub fn demultiplex(cf: &Config) -> Result<(), Error> {
     let in_allocs_reused = Arc::new(Mutex::new(0));
 
     // Spawn one reader thread per input (infix) file.
-    let mut readers: [_; INPUT_INFIXES.len()] = array::try_from_fn(|i| -> Result<_, Error> {
+    let mut readers: [_; INPUT_INFIXES.len()] = fun::try_from_fn(|i| -> Result<_, Error> {
         let infix = INPUT_INFIXES[i];
         let path = &cf.inputs[i];
         println!("  {}", path.display().to_string().blue());
@@ -110,7 +106,7 @@ pub fn demultiplex(cf: &Config) -> Result<(), Error> {
     //----------------------------------------------------------------------------------------------
     println!("Read all expected modules from references.");
     let [mut ref_a, mut ref_b, mut ref_c, mut ref_d] = // (mutable because of their internal caches)
-        array::try_from_fn(|i| -> Result<_, Error> {
+        fun::try_from_fn(|i| -> Result<_, Error> {
             let letter = MODULE_LETTERS[i];
             let path = &cf.references[i];
             println!("  {}", path.display().to_string().blue());
@@ -167,6 +163,7 @@ pub fn demultiplex(cf: &Config) -> Result<(), Error> {
     for (&code, sample_id) in &samples.map {
         all_writers.insert(
             code,
+            #[allow(unstable_name_collisions)] // Purposedly downgrading from nightly.
             OUTPUT_INFIXES.try_map(|infix| -> Result<_, Error> {
                 // Create writer with zip | disk IO stream.
                 let path = (cf.sample)(&ful(sample_id), infix);
diff --git a/src/parse.rs b/src/parse.rs
index 45ba5c2..afd9683 100644
--- a/src/parse.rs
+++ b/src/parse.rs
@@ -1,5 +1,5 @@
-pub(crate) mod modules;
 pub(crate) mod fastq;
+pub(crate) mod modules;
 pub(crate) mod samples;
 
 pub(crate) use modules::parse as modules;
diff --git a/src/parse/modules.rs b/src/parse/modules.rs
index b3965d8..ab3074e 100644
--- a/src/parse/modules.rs
+++ b/src/parse/modules.rs
@@ -8,6 +8,7 @@ use snafu::{ensure, OptionExt, ResultExt, Snafu};
 
 use crate::{
     demux::Config,
+    from_unstable::SplitOnce,
     io::{self, Context},
     short::ful,
     ContextualizedReader,
@@ -59,9 +60,8 @@ fn extract_code<'l>(
     cf: &Config,
 ) -> Result<&'l [u8], ParseError> {
     let &Config { module_size, .. } = cf;
-    let (id, code) = line
-        .split_once(|&b| b == b'\t')
-        .with_context(|| LineErr { line })?;
+    #[allow(unstable_name_collisions)] // Purposedly downgrading from nightly.
+    let (id, code) = line.split_once(b'\t').with_context(|| LineErr { line })?;
 
     // Parse id and check consistency with line number.
     let id = id
diff --git a/src/parse/samples.rs b/src/parse/samples.rs
index 6fb93ae..49d01a5 100644
--- a/src/parse/samples.rs
+++ b/src/parse/samples.rs
@@ -11,7 +11,8 @@ use snafu::{ensure, OptionExt, ResultExt, Snafu};
 
 use crate::{
     ful,
-    io::{self, Context, ContextualizedReader}, MODULE_LETTERS,
+    io::{self, Context, ContextualizedReader},
+    MODULE_LETTERS,
 };
 
 #[derive(Debug)]
-- 
GitLab