Conversation
8663422 to
7160959
Compare
src/types/list.rs
Outdated
| { | ||
| fn from_iter<T: IntoIterator<Item = A>>(iter: T) -> Self { | ||
| // TODO(bschoenmaeckers): Merge this impl with `PyList::new` when `try_as_dyn` is stable. | ||
| Python::attach(|py| { |
There was a problem hiding this comment.
I generally think we should avoid impls that internally need to call Python::attach. It's surprising and not really visible to the user and thus can easily lead to deadlocks. For the same reason we also removed the Clone impl of Py.
Adding this to Bound<PyList> could be an option however.
There was a problem hiding this comment.
Another option is to require the item type to be Bound<'py, PyAny> - that way the items guarantee the thread is attached.
That requires callers to do the work with into_pyobject(), but that's possibly a good thing anyway because it would avoid the need for the Infallible constraint.
There was a problem hiding this comment.
I was also thinking about this. But I was concerned it might let users sneak out a python token using a empty iterator.
There was a problem hiding this comment.
Ah, great catch. The following indeed segfaults:
impl<'py> FromIterator<Bound<'py, PyAny>> for Bound<'py, PyList> {
fn from_iter<T: IntoIterator<Item = Bound<'py, PyAny>>>(_iter: T) -> Self {
PyList::empty(unsafe { Python::assume_attached() })
}
}
#[test]
fn test_fabricate_list() {
std::iter::empty().collect::<Bound<'_, PyList>>();
}It's interesting to conclude that Python::assume_attached() is unsound in methods that take optional bound values - I think I had not previously realised this, and assumed the presence of the 'py lifetime in Option<Bound<'py, PyAny>> was enough to create soundness.
... searching the codebase briefly, I didn't spot any places where this is currently an issue.
There was a problem hiding this comment.
I could panic (maybe only in debug?) when a user tries to call this without a attached thread. And by only implementing on Py<PyList> we prevent leaking an unbounded python token. Would this be sufficient?
impl<'py> FromIterator<Bound<'py, PyAny>> for Py<PyList>
{
fn from_iter<T: IntoIterator<Item = Bound<'py, PyAny>>>(iter: T) -> Self {
let mut elements = iter.into_iter().peekable();
let py = elements.peek().map_or_else(|| {
assert!(thread_is_attached());
unsafe { Python::assume_attached() }
}, |item| item.py());
PyList::new(py, elements)
.expect("Could not allocate enough memory to add elements to the list")
.unbind()
}
}There was a problem hiding this comment.
I think that adding the Infallible constraint would make this more flexible and makes it more obvious that this function does not unwrap conversion failures. What do you think?
impl<'py, A> FromIterator<Bound<'py, A>> for Py<PyList>
where
Bound<'py, A>: IntoPyObject<'py, Error = Infallible>,There was a problem hiding this comment.
I'm also fine with the following:
impl<'py, A> FromIterator<Bound<'py, A>> for Py<PyList>
where
Bound<'py, A>: IntoPyObject<'py>,There was a problem hiding this comment.
I have to say that personally I'm not convinced that we should add this under these circumstances. Just using PyList::new directly seems like the cleaner solution to me.
There was a problem hiding this comment.
I think I'm coming to the same conclusion, especially after #4782 (comment) which we could document as an option for fallible iterators
src/types/list.rs
Outdated
| /// This may be slower than using [`PyList::new`], because it cannot reserve enough space ahead of | ||
| /// time when the iterator is not [`ExactSizeIterator`] |
There was a problem hiding this comment.
We should consider is relaxing the need for PyList::new to receive ExactSizeIterator, otherwise this implementation provides a (possibly unexpected) way to get around that.
Not sure of the pros/cons to doing that.
There was a problem hiding this comment.
I would love to relax the bound on PyList::new but this will make it impossible to implement the upper bound check that is currently in place. try_as_dyn would solve this in the future.
There was a problem hiding this comment.
Can we just drop this check and do the same thing as your FromIterator implementation by just calling append on the extra elements? This way we provide a more usable API and avoid the try_as_dyn requirement
There was a problem hiding this comment.
We could drop the check, but let's see if other maintainers think that is desirable.
There was a problem hiding this comment.
I think calling append on the extra elements is relatively fine (it's what Vec would do, after all), however I think the bigger downside is when there's fewer elements in the iterator than expected.
Because we have to set the list to the expected size to the list up-front, we'd need to "delete" the invalid entries off the end of the list, possibly first pre-filling with a sentinel value such as None.
I'm open to seeing how that looks, however that's probably fiddly enough it's worth doing in its own PR separate to this one.
I also am not sure if we can do similar for PyTuple (if we even should).
There was a problem hiding this comment.
Can't we just trust the lower bound on size_hint and panic if the iterator produces less than that?
src/types/list.rs
Outdated
| { | ||
| fn from_iter<T: IntoIterator<Item = A>>(iter: T) -> Self { | ||
| // TODO(bschoenmaeckers): Merge this impl with `PyList::new` when `try_as_dyn` is stable. | ||
| Python::attach(|py| { |
There was a problem hiding this comment.
Another option is to require the item type to be Bound<'py, PyAny> - that way the items guarantee the thread is attached.
That requires callers to do the work with into_pyobject(), but that's possibly a good thing anyway because it would avoid the need for the Infallible constraint.
7160959 to
82b5b40
Compare
82b5b40 to
7ffddf7
Compare
closes #4782