1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
//! A wrapper around Scotch bindings.
//!
//! This crate provides a thin but idiomatic API for libscotch.
//!
//! # Example
//!
//! Here is an example of graph partitioning:
//!
//! ```rust
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let partnbr = 3; // divide the graph in three parts.
//! let mut strat = scotch::Strategy::new(); // use the default strategy.
//! let arch = scotch::Architecture::complete(partnbr); // all parts are equal.
//! let mut graph = scotch::Graph::from_file("testdata/grid.grf", -1)?; // load a graph file.
//!
//! // Partition the graph:
//! let (vertnbr, _edgenbr) = graph.size();
//! let mut parttab = vec![0; vertnbr as usize];
//! graph
//!     .mapping(&arch, &mut parttab)
//!     .compute(&mut strat)?
//!     .write_to_stdout()?;
//! # Ok(())
//! # }
//! ```

#![allow(unused_unsafe)]
#![warn(clippy::doc_markdown)]
#![deny(missing_docs)]

pub use crate::architecture::Architecture;
pub use crate::graph::Graph;
pub use crate::strategy::Strategy;
use scotch_sys as s;
use std::fmt;
use std::io;
use std::os;

pub mod architecture;
pub mod graph;
pub mod strategy;

#[cfg(not(unix))]
compile_error!("Scotch is only supported on UNIX platforms");

#[cfg(test)]
#[test]
fn bindings_are_for_the_correct_version_of_scotch() {
    const BINDINGS_VERSION: u32 = 6;
    assert!(
        s::SCOTCH_VERSION == BINDINGS_VERSION,
        "Rust bindings to Scotch have been made for Scotch {}, your version of Scotch is {}",
        BINDINGS_VERSION,
        s::SCOTCH_VERSION,
    );
}

/// Scotch's numeral type.
///
/// It is defined as a signed C `int`.  In most platforms it maps to an [i32].  However it can also
/// map to an [i16].  In both cases, most functions and associated constants from both types will
/// work.
pub type Num = s::SCOTCH_Num;

#[cfg(debug_assertions)]
fn trusted_num_to_usize(n: Num) -> usize {
    usize::try_from(n).unwrap_or_else(|_| panic!("Scotch returned a bad size: {}", n))
}

#[cfg(not(debug_assertions))]
fn trusted_num_to_usize(n: Num) -> usize {
    n as usize
}

/// Error type for Scotch functions.
///
/// # Error handling
///
/// Scotch doesn't provide a way to differentiate errors.  When an error is
/// returned by a function, Scotch will typically already have printed an error
/// message on standard error.
///
/// Some errors are hidden from these bindings as panics, that is the Rust
/// function panics if Scotch returns an error. This should only happen when
/// there is a mismatch between Scotch's target architecture and its
/// configuration (e.g. type size mismatch).  These checks are enabled when
/// Scotch is built with debug assertions enabled.
#[derive(Debug)]
#[non_exhaustive]
pub struct Error {}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Scotch function returned an error")
    }
}

impl std::error::Error for Error {}

/// Convenience wrapper around [`std::result::Result`] which bears the Scotch
/// error type.
///
/// See [`Error`] for notes on error handling.
pub type Result<T> = std::result::Result<T, Error>;

trait ErrorCode {
    /// Makes a [Result] from a return code (int) from Scotch.
    fn wrap(self) -> Result<()>;
}

impl ErrorCode for os::raw::c_int {
    fn wrap(self) -> Result<()> {
        if self == 0 {
            Ok(())
        } else {
            Err(Error {})
        }
    }
}

/// Convenience wrapper around [`s::fdopen`].
///
/// # Safety
///
/// Two assumptions are made and must be uphold by the caller:
///
/// - `fd` must be valid for reading or writing, depending on the given `mode`,
/// - the `mode` string must end with a nul byte.
unsafe fn fdopen(fd: os::unix::io::RawFd, mode: &str) -> io::Result<*mut s::FILE> {
    let file = s::fdopen(fd, mode.as_ptr() as *const i8);
    if file.is_null() {
        return Err(io::Error::last_os_error());
    }
    Ok(file)
}