Использование *const T and *mut T


#1

Пришел к следующему выводу, во всех случаях, когда все под вашем контролем, и вы можете уверено гарантировать правильность использования указателей, (например когда это часть внутренней кухни вашей библиотеки)

использование сырых *const T and *mut T не только вредно, но и полезно. :wink:
особенно это касается static переменных.

а вот внешние интерфейсы, где возможны не подконтрольные вариации, наоборот использование *const T and *mut T лучше избегать

вот такое “открытие” нуба.
было бы интересно услышать возражения,

если у кого нибудь есть какие-то подобные, очевидно-не очевидные, наблюдения… делитесь


#2

Весь мой опыт толсто намекает мне, что чего-то нетривиальное уверенно гарантировать, если компилятор (или еще что автоматическое) меня не подстраховывает, я не особо могу. Даже если речь идет чисто о внутренностях какой-то моей библиотеки. Использование сырых указателей лишает львиной доли преимуществ ржавчины, так что если без них реально решить поставленную задачу, то лучше их и не расчехлять лишний раз.

использование сырых *const T and *mut T … полезно … особенно это касается static переменных.

Тут хочется подробнее про решаемую задачу и сравнение с безопасным вариантом.


#3

дополню:

static - поскольку они существуют все время работы программы И это
либо однопоточное приложение (что бывает часто)
либо этот статик упрятан в локальное хранилище потока, если приложение многопоточное.

просто я написал код и наличие проверок вынуждало делать кучу оберток, которые просто не нужны… лишком все тривиально.

переписал на указателях - код существенно упростился.


#4

А примеры кода до и после можешь показать? Обычно меня всякие вспомогательные функции и ? выручали, но может и правда в этой ситуации оправдано.


#5

всё. тема инициализации static стала не актуальной.
решение, c минимальными издержками, найдено…

# ![feature(untagged_unions)]

union Static<T>  {
	uninited: (),
	inited: T,
}

// Resources:
static mut FOO: Static<FOO_TYPE> = Static{ uninited: () };

fn main() {
    let foo = get_FOO_TYPE(...);
    unsafe { FOO = Static{ inited: foo }; }
}


#6

А уже готовый https://github.com/rust-lang-nursery/lazy-static.rs почему не подходит?


#7

У меня есть такой код получение/установки значений полей Toml, который я не знаю как реализовать без сырых указателей:

fn set_toml(toml: &mut Value, path: impl AsRef<str>, value: Value) -> Result<(), Error> {
    let mut cursor = toml as *mut Value;
    let path_vec = path.as_ref()
        .split(unsafe { SEPARATOR })
        .collect::<Vec<&str>>();
    let mut next_iter = path_vec.iter().skip(1);

    // проверяем структуру
    for (current, next) in path_vec.iter().map(|current| (current, next_iter.next())) {
        // Если есть следующий элемент то создаем "папку"
        if let Some(next) = next {
            if let Ok(index) = current.parse::<usize>() {
                if let Value::Array(ref mut array) = unsafe { &mut *cursor } {
                    let empty = if let Ok(_) = next.parse::<usize>() {
                        Value::Array(Vec::new())
                    } else {
                        Value::Table(BTreeMap::new())
                    };
                    for _ in array.len()..index + 1 {
                        array.push(empty.clone());
                    }
                    cursor = &mut array[index] as *mut Value;
                } else {
                    return Err(ErrorKind::RedefineTypeError(current.to_string()).into());
                }
            } else {
                if let Value::Table(ref mut table) = unsafe { &mut *cursor } {
                    if !table.contains_key(&current.to_string()) {
                        if let Ok(_) = next.parse::<usize>() {
                            table.insert(current.to_string(), Value::Array(Vec::new()));
                        } else {
                            table.insert(current.to_string(), Value::Table(BTreeMap::new()));
                        }
                    }
                    cursor = table.get_mut(&current.to_string()).unwrap() as *mut Value;
                } else {
                    return Err(ErrorKind::RedefineTypeError(current.to_string()).into());
                }
            }
        // Если следующего элемента нет создаем "файл"
        } else {
            match unsafe { &mut *cursor } {
                Value::Array(ref mut array) => match current.parse::<usize>() {
                    Ok(index) => {
                        for _ in array.len()..index + 1 {
                            array.push(value.clone());
                        }
                        if !array[index].same_type(&value) {
                            return Err(ErrorKind::RedefineTypeError(current.to_string()).into());
                        }
                        array[index] = value.clone();
                    }
                    Err(err) => {
                        return Err(ErrorKind::IndexParseError(current.to_string(), err).into());
                    }
                },
                Value::Table(ref mut table) => {
                    if let Some(old) = table.get(&current.to_string()) {
                        if !old.same_type(&value) {
                            return Err(ErrorKind::RedefineTypeError(current.to_string()).into());
                        }
                    }
                    table.insert(current.to_string(), value.clone());
                }
                _ => {
                    return Err(ErrorKind::RedefineTypeError(current.to_string()).into());
                }
            }
        }
    }
    Ok(())
}

fn get_toml<'a>(toml: &'a Value, path: impl AsRef<str>) -> Result<Option<&'a Value>, Error> {
    let mut cursor = toml;
    let path_vec = path.as_ref()
        .split(unsafe { SEPARATOR })
        .collect::<Vec<&str>>();
    let mut next_iter = path_vec.iter().skip(1);

    for (current, next) in path_vec.iter().map(|current| (current, next_iter.next())) {
        if let Some(_) = next {
            match cursor {
                Value::Array(array) => {
                    if let Ok(index) = current.parse::<usize>() {
                        if index < array.len() {
                            cursor = &array[index];
                        } else {
                            return Ok(None);
                        }
                    } else {
                        return Ok(None);
                    }
                }
                Value::Table(table) => {
                    if let Some(index) = table.get(&current.to_string()) {
                        cursor = index;
                    } else {
                        return Ok(None);
                    }
                }
                _ => {
                    return Ok(None);
                }
            }
        } else {
            match cursor {
                Value::Array(array) => {
                    if let Ok(index) = current.parse::<usize>() {
                        if index < array.len() {
                            return Ok(Some(&array[index]));
                        } else {
                            return Ok(None);
                        }
                    } else {
                        return Ok(None);
                    }
                }
                Value::Table(table) => {
                    if let Some(index) = table.get(&current.to_string()) {
                        return Ok(Some(index));
                    } else {
                        return Ok(None);
                    }
                }
                _ => {
                    return Ok(None);
                }
            }
        }
    }
    Ok(None)
}

#8

Так вроде получается


#9

Спасибо. А могло быть такое, что старая версия компилятора ругалась borrow-checker’ом?


#10

А нет!
Не понимаю почему в плейграунде этот код компилируется, но у меня такая ошибка:

error[E0499]: cannot borrow `cursor.0` as mutable more than once at a time
   --> config/src/lib.rs:142:37
    |
142 |                 if let Value::Array(ref mut array) = cursor {
    |                                     ^^^^^^^^^^^^^ mutable borrow starts here in previous iteration of loop
...
201 | }
    | - mutable borrow ends here

error[E0506]: cannot assign to `cursor` because it is borrowed
   --> config/src/lib.rs:151:21
    |
142 |                 if let Value::Array(ref mut array) = cursor {
    |                                     ------------- borrow of `cursor` occurs here


#11

а что это за магия:
if let Value::Array(ref mut array) = { cursor }
vs
if let Value::Array(ref mut array) = cursor


#12

Это перемещение содержимого cursor в неявную временную переменную.
В принципе можно написать так:

let t = cursor;
if let Value::Array(ref mut array) = t {
    //...
    cursor = &mut array[index];
}

или немного короче:


if let Value::Array(ref mut array) = {let t = cursor; t} {
    //...
    cursor = &mut array[index];
}

Теперь array заимствует не cursor а временную переменную t. И мы можем записать новое значение в cursor.


Вот небольшая статья про эту магию:
https://bluss.github.io/rust/fun/2015/10/11/stuff-the-identity-function-does/


#13

Спасибо.
Я посмотрел 2018-edition это исправляет.